Linux电源管理-wakeup events framework

前言

通常新机制/事物的出现往往是解决某些问题的,同样wakeup events framework机制也不例外。先带大家了解下wakeup events framework出现的背景,然后在了解其内部的实现机制。
Linux系统中的电源管理一般是冷睡眠,而Android系统却将linux系统中的睡眠作为通常待机使用,显然Linux中的电源管理不符合Android系统。Android说既然不符合,我就给你改到符合,早期Android就提出了"wakelocks"机制,这种机制将Linux原生的睡眠唤醒流程改变,增加Android自己的处理函数,在一段时间这种机制可以解决Android上的省电,节能问题。但是有一种问题就是suspend和wakeup events之间的同步问题。当系统发生了suspend操作,系统会freeze process,  device prepared, device suspend,disabled irq等,这时候假设有wakeup events产生,而此时系统无法从suspend过程中唤醒。所以Linux在2.6.36中引入了wakeup events framework机制,用来解决suspend和wakeup events之间的同步问题。在Android4.4中,也去掉了之前的"wakelocks"机制,Andoird利用wakeup events framework重新设计了wakelocks,而上层API保持不变。

详细可参考:  http://lwn.net/Articles/388131/    或者https://lwn.net/Articles/416690/

数据结构

wakeup events framework代码在:  /kernel/drivers/base/power/wakeup.c中实现。在wakeup events framework中重要的数据结构就是wakeup_source,字面意思就是产生wakeup events的设备。

/**
 * struct wakeup_source - Representation of wakeup sources
 *
 * @total_time: Total time this wakeup source has been active.
 * @max_time: Maximum time this wakeup source has been continuously active.
 * @last_time: Monotonic clock when the wakeup source's was touched last time.
 * @prevent_sleep_time: Total time this source has been preventing autosleep.
 * @event_count: Number of signaled wakeup events.
 * @active_count: Number of times the wakeup source was activated.
 * @relax_count: Number of times the wakeup source was deactivated.
 * @expire_count: Number of times the wakeup source's timeout has expired.
 * @wakeup_count: Number of times the wakeup source might abort suspend.
 * @active: Status of the wakeup source.
 * @has_timeout: The wakeup source has been activated with a timeout.
 */
struct wakeup_source {
	const char 		*name;
	struct list_head	entry;
	spinlock_t		lock;
	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;
};
.name:    唤醒源的名字。
.entry:     用来将唤醒源挂到链表上,用于管理。
.lock:       同步机制,用于访问链表时使用。
.timer:     定时器,用于设置该唤醒源的超时时间。
.timer_expires:  定时器的超时时间。
.total_time:  wakeup source处于active状态的总时间。
.max_time:  wakeup source处于active状态的最长时间。
.last_time:   wakeup source处于active状态的上次时间。
.start_prevent_time:   wakeup source阻止autosleep的开始时间。
.prevent_sleep_time:  wakeup source阻止autosleep的总时间。
.event_count:  wakeup source上报wakeup event的个数。
.active_count: wakeup source处于active状态的次数。
.relax_count:  wakeup source处于deactive状态的次数。
.expire_count:  wakeup source timeout次数。
.wakeup_count:  wakeup source abort睡眠的次数。
.active:  wakeup source的状态。
.autosleep_enabled:  autosleep使能的状态。

那到底什么是唤醒源呢? 在linux系统中,只有具有唤醒系统的设备才叫做“wakeup source”。 既然只有设备才能唤醒系统,那设备结构体struce device中就应该有某种标志代表此设备是否具有唤醒的能力。
struct device {
    ...
	struct dev_pm_info	power; 
	struct dev_pm_domain	*pm_domain;
	...
}
其中dev_pm_info代表该设备pm相关的详细信息。
struct dev_pm_info {
	pm_message_t		power_state;
	unsigned int		can_wakeup:1;
	unsigned int		async_suspend:1;
	
    ...
#ifdef CONFIG_PM_SLEEP
	struct list_head	entry;
	struct completion	completion;
	struct wakeup_source	*wakeup;
	bool			wakeup_path:1;
	bool			syscore:1;
#else
	unsigned int		should_wakeup:1;
#endif
	...
}
其中can_wakeup就代表该设备是否具有唤醒系统的能力。只有具有唤醒能力的device,在sys/devices/xxx/下就会存在power相关目录的。

Sys接口

为了方便查看系统的wakeup sources,linux系统在/sys/kernel/debug下创建了一个"wakeup_sources"文件,此文件记录了系统的唤醒源的详细信息。
static int wakeup_sources_stats_show(struct seq_file *m, void *unused)
{
	struct wakeup_source *ws;

	seq_puts(m, "name\t\tactive_count\tevent_count\twakeup_count\t"
		"expire_count\tactive_since\ttotal_time\tmax_time\t"
		"last_change\tprevent_suspend_time\n");

	rcu_read_lock();
	list_for_each_entry_rcu(ws, &wakeup_sources, entry)
		print_wakeup_source_stats(m, ws);
	rcu_read_unlock();

	return 0;
}

static int wakeup_sources_stats_open(struct inode *inode, struct file *file)
{
	return single_open(file, wakeup_sources_stats_show, NULL);
}

static const struct file_operations wakeup_sources_stats_fops = {
	.owner = THIS_MODULE,
	.open = wakeup_sources_stats_open,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
};

static int __init wakeup_sources_debugfs_init(void)
{
	wakeup_sources_stats_dentry = debugfs_create_file("wakeup_sources",
			S_IRUGO, NULL, NULL, &wakeup_sources_stats_fops);
	return 0;
}
以下是手机上的wakeup sources信息
root@test:/ # cat /sys/kernel/debug/wakeup_sources                    
name		active_count	event_count	wakeup_count	expire_count	active_since	total_time	max_time	last_change	prevent_suspend_time
event1      	40644		40644		0		0		0		31294		30		537054822		0
event4      	4496		4496		0		0		0		13369		22		20913677		0
event5      	4496		4496		0		0		0		13048		22		20913677		0
event0      	4540		4540		0		0		0		27995		277		258270184		0
eventpoll   	40688		54176		0		0		0		217		5		537054822		0
NETLINK     	2175		2175		0		0		0		16960		59		537058523		0
event_count:   代表wakeup source上报wakeup event的个数。
active_count:  当wakeup source产生wakeup events之后,wakup source的状态就处于active。但并不是每次都需要激活该wakup source,如果该wakeup source已经处于激活状态,则就不再需要激活。从一定角度可以说产生该wakup source设备的繁忙程度。
wakeup_count:  当系统在suspend的过程中,如果有wakeup source产生了wakup events事件,就会终止suspend的过程。该变量就记录了终止suspend的次数。

相关API

  • pm_stay_awake(有wakeup events产生后调用此函数通知PMcore)
void pm_stay_awake(struct device *dev)
{
	unsigned long flags;

	if (!dev)
		return;

	spin_lock_irqsave(&dev->power.lock, flags);
	__pm_stay_awake(dev->power.wakeup);
	spin_unlock_irqrestore(&dev->power.lock, flags);
该函数直接就调用__pm_stay_awake函数。此函数可以在中断上下文使用。
void __pm_stay_awake(struct wakeup_source *ws)
{
	unsigned long flags;

	if (!ws)
		return;

	spin_lock_irqsave(&ws->lock, flags);

	wakeup_source_report_event(ws);
	del_timer(&ws->timer);
	ws->timer_expires = 0;

	spin_unlock_irqrestore(&ws->lock, flags);
}
当wakeup source产生wakup events之后,调用pm_stay_awake函数上报wakeup events。随后会调用pm_relax函数,通知PM core wakeup events已经处理完毕。所以在__pm_stay_awake中不需要定时器。随后调用wakeup_source_report_event上报wakup events。
static void wakeup_source_report_event(struct wakeup_source *ws)
{
	ws->event_count++;
	/* This is racy, but the counter is approximate anyway. */
	if (events_check_enabled)
		ws->wakeup_count++;

	if (!ws->active)
		wakeup_source_activate(ws);
}
1.  wakeup events个数加1,也就是event_count加1。
2.  如果events_check_enabled设置了,则会终止系统suspend/hibernate,此时就需要将wakup_count加1, 代表阻止了suspend的次数。
/*
 * If set, the suspend/hibernate code will abort transitions to a sleep state
 * if wakeup events are registered during or immediately before the transition.
 */
bool events_check_enabled __read_mostly;
3. 如果wakup source没有激活的,激活该wakup source。假如已经处于active状态,则event_count就比active_count大。
static void wakeup_source_activate(struct wakeup_source *ws)
{
	unsigned int cec;

	/*
	 * active wakeup source should bring the system
	 * out of PM_SUSPEND_FREEZE state
	 */
	freeze_wake();

	ws->active = true;
	ws->active_count++;
	ws->last_time = ktime_get();
	if (ws->autosleep_enabled)
		ws->start_prevent_time = ws->last_time;

	/* Increment the counter of events in progress. */
	cec = atomic_inc_return(&combined_event_count);

	trace_wakeup_source_activate(ws->name, cec);
}
1.  调用freeze_wake将系统从FREEZE状态唤醒。
2.  更新wakeup source的active的状态。
3.  增加wakeup source的actice_count的引用计数。
4.  设置wakup source的last_time。
5.  如果autosleep enable,设置开始阻止的时间,因为从现在开始就阻止了autosleep。
6.  "wakeup events in progress"加1。"wakeup events in progress"代表系统中有wakeup events正在处理中,不为0,系统不能suspend。
  • pm_relax(唤醒事件处理完毕后,调用该函数通知PM core)
void pm_relax(struct device *dev)
{
	unsigned long flags;

	if (!dev)
		return;

	spin_lock_irqsave(&dev->power.lock, flags);
	__pm_relax(dev->power.wakeup);
	spin_unlock_irqrestore(&dev->power.lock, flags);
}
该函数也是直接调用__pm_relax函数。
void __pm_relax(struct wakeup_source *ws)
{
	unsigned long flags;

	if (!ws)
		return;

	spin_lock_irqsave(&ws->lock, flags);
	if (ws->active)
		wakeup_source_deactivate(ws);
	spin_unlock_irqrestore(&ws->lock, flags);
}
如果该wakeup source已经处于active状态,则调用wakeup_source_deactivate函数deactivce之。
static void wakeup_source_deactivate(struct wakeup_source *ws)
{
	unsigned int cnt, inpr, cec;
	ktime_t duration;
	ktime_t now;

	ws->relax_count++;
	/*
	 * __pm_relax() may be called directly or from a timer function.
	 * If it is called directly right after the timer function has been
	 * started, but before the timer function calls __pm_relax(), it is
	 * possible that __pm_stay_awake() will be called in the meantime and
	 * will set ws->active.  Then, ws->active may be cleared immediately
	 * by the __pm_relax() called from the timer function, but in such a
	 * case ws->relax_count will be different from ws->active_count.
	 */
	if (ws->relax_count != ws->active_count) {
		ws->relax_count--;
		return;
	}

	ws->active = false;

	now = ktime_get();
	duration = ktime_sub(now, ws->last_time);
	ws->total_time = ktime_add(ws->total_time, duration);
	if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time))
		ws->max_time = duration;

	ws->last_time = now;
	del_timer(&ws->timer);
	ws->timer_expires = 0;

	if (ws->autosleep_enabled)
		update_prevent_sleep_time(ws, now);

	/*
	 * Increment the counter of registered wakeup events and decrement the
	 * couter of wakeup events in progress simultaneously.
	 */
	cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);
	trace_wakeup_source_deactivate(ws->name, cec);

	split_counters(&cnt, &inpr);
	if (!inpr && waitqueue_active(&wakeup_count_wait_queue))
		wake_up(&wakeup_count_wait_queue);
}
1.  wakeup source的deactive状态加1, 也就是relax_count加1。
2.  将wakeup source的状态设置为false。
3.  计算wakeup event处理的时间,然后设置total time,  last_time,  max_time。
4.  如果autosleep使能,更新prevent_sleep_time
static void update_prevent_sleep_time(struct wakeup_source *ws, ktime_t now)
{
	ktime_t delta = ktime_sub(now, ws->start_prevent_time);
	ws->prevent_sleep_time = ktime_add(ws->prevent_sleep_time, delta);
}
5.  增加"registered wakeup events"同时减少“wakeup events in progress”。
6.  wakeup count相关的处理,留到wakeup count小节分析。总之简单理解就是激活active的反操作。
  • device_init_wakeup(wakeup source初始化操作,通常在设备驱动中使用该接口)
int device_init_wakeup(struct device *dev, bool enable)
{
	int ret = 0;

	if (!dev)
		return -EINVAL;

	if (enable) {
		device_set_wakeup_capable(dev, true);
		ret = device_wakeup_enable(dev);
	} else {
		if (dev->power.can_wakeup)
			device_wakeup_disable(dev);

		device_set_wakeup_capable(dev, false);
	}

	return ret;
}
1.  如果enable等于true,则设置device wakeup capability flag。然后enable wakeup source。
2.  如果enable等于false, 则disable wakeup source, 已经disable device wakeup capability flag。
  • device_set_wakeup_capable(设置device是否有将系统从sleep唤醒的能力)
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;
}
如果capable等于ture, 则设置power.can_wakup的flag, 然后添加wakup属性到sys中。如果capable等于false,将wakeup属性从sys中移除,然后设置can_wakeup属性。wakup属性定义如下:
static struct attribute *wakeup_attrs[] = {
#ifdef CONFIG_PM_SLEEP
	&dev_attr_wakeup.attr,
	&dev_attr_wakeup_count.attr,
	&dev_attr_wakeup_active_count.attr,
	&dev_attr_wakeup_abort_count.attr,
	&dev_attr_wakeup_expire_count.attr,
	&dev_attr_wakeup_active.attr,
	&dev_attr_wakeup_total_time_ms.attr,
	&dev_attr_wakeup_max_time_ms.attr,
	&dev_attr_wakeup_last_time_ms.attr,
#ifdef CONFIG_PM_AUTOSLEEP
	&dev_attr_wakeup_prevent_sleep_time_ms.attr,
#endif
#endif
	NULL,
};
static struct attribute_group pm_wakeup_attr_group = {
	.name	= power_group_name,
	.attrs	= wakeup_attrs,
};
关于读取/设置wakeup的属性这里不再详细说明,前面已经见过很多次了。
  • device_wakeup_enable(enable device to be a wakeup source)
int device_wakeup_enable(struct device *dev)
{
	struct wakeup_source *ws;
	int ret;

	if (!dev || !dev->power.can_wakeup)
		return -EINVAL;

	ws = wakeup_source_register(dev_name(dev));
	if (!ws)
		return -ENOMEM;

	ret = device_wakeup_attach(dev, ws);
	if (ret)
		wakeup_source_unregister(ws);

	return ret;
}
1.  如果device不存在或者device不具有wakeup能力,则返回-EINVAL。
2.  创建wakeup source。注册wakeup source。
3.  将设备和wakeup source建立连接。如果失败,则释放wakeup source。
  • wakeup_source_register(分配一个唤醒源,将其加入到wakeup source链表中)
struct wakeup_source *wakeup_source_register(const char *name)
{
	struct wakeup_source *ws;

	ws = wakeup_source_create(name);
	if (ws)
		wakeup_source_add(ws);

	return ws;
}
1.  分配一个wakeup_source结构体,然后设置该wakeup source的name域。
struct wakeup_source *wakeup_source_create(const char *name)
{
	struct wakeup_source *ws;

	ws = kmalloc(sizeof(*ws), GFP_KERNEL);
	if (!ws)
		return NULL;

	wakeup_source_prepare(ws, name ? kstrdup(name, GFP_KERNEL) : NULL);
	return ws;
}
2. 添加给定的wakeup source到wakup source链表中。
void wakeup_source_add(struct wakeup_source *ws)
{
	unsigned long flags;

	if (WARN_ON(!ws))
		return;

	spin_lock_init(&ws->lock);
	setup_timer(&ws->timer, pm_wakeup_timer_fn, (unsigned long)ws);
	ws->active = false;
	ws->last_time = ktime_get();

	spin_lock_irqsave(&events_lock, flags);
	list_add_rcu(&ws->entry, &wakeup_sources);
	spin_unlock_irqrestore(&events_lock, flags);
}
其中还包括初始化spinlock,  建立定时器,然后将该wakeup source添加到wakeup_sources链表中。
次数,如果定时器设置的时间超时,则会调用定时器超时函数,在超时函数中deactive wakeup source, 然后超时count加1。
static void pm_wakeup_timer_fn(unsigned long data)
{
	struct wakeup_source *ws = (struct wakeup_source *)data;
	unsigned long flags;

	spin_lock_irqsave(&ws->lock, flags);

	if (ws->active && ws->timer_expires
	    && time_after_eq(jiffies, ws->timer_expires)) {
		wakeup_source_deactivate(ws);
		ws->expire_count++;
	}

	spin_unlock_irqrestore(&ws->lock, flags);
}
  • device_wakeup_attach(将wakeup source和device建立连接)
static int device_wakeup_attach(struct device *dev, struct wakeup_source *ws)
{
	spin_lock_irq(&dev->power.lock);
	if (dev->power.wakeup) {
		spin_unlock_irq(&dev->power.lock);
		return -EEXIST;
	}
	dev->power.wakeup = ws;
	spin_unlock_irq(&dev->power.lock);
	return 0;
}
如果此设备的wakeup source已经存在,则返回。如果没有则通过传入进来的wakeup source设置。
  • pm_wakeup_event(唤醒wakeup source, 在一段时间之后取消唤醒源)
void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec)
{
	unsigned long flags;
	unsigned long expires;

	if (!ws)
		return;

	spin_lock_irqsave(&ws->lock, flags);

	wakeup_source_report_event(ws);

	if (!msec) {
		wakeup_source_deactivate(ws);
		goto unlock;
	}

	expires = jiffies + msecs_to_jiffies(msec);
	if (!expires)
		expires = 1;

	if (!ws->timer_expires || time_after(expires, ws->timer_expires)) {
		mod_timer(&ws->timer, expires);
		ws->timer_expires = expires;
	}

 unlock:
	spin_unlock_irqrestore(&ws->lock, flags);
}
首先需要激活wakeup source, 然后如果超时时间是零,则立马又deactive wakeup source, 否则在一段时间之后deactive wakeup source。

示例分析

既然明白了wakeup events framework机制,那驱动程序中应该如何使用呢? 既然不知道如何使用,那就在kernel代码中寻找答案。

1.  一个设备既然要作用唤醒源,必须调用wakeup events framework提供的接口函数,而device_init_wakeup函数就具有此功能,而且还是外部的。在内核中搜索该函数的使用。这时候你会发现有好多处都调用此函数,则就可以顺着此思路探索下去。(kernel/drivers/input/keyboard/gpio-keys.c)

在probe函数中会设置workqueue,  设置timer,  设置wakeup source
INIT_WORK(&bdata->work, gpio_keys_gpio_work_func);
setup_timer(&bdata->timer,gpio_keys_gpio_timer, (unsigned long)bdata);
device_init_wakeup(&pdev->dev, wakeup);

2.  在key按下之后就会调用key的中断处理函数。
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
    ...

	if (bdata->button->wakeup)
		pm_stay_awake(bdata->input->dev.parent);
    ...
    
	return IRQ_HANDLED;
}
调用pm_stay_awake通知PM core,有wake events 产生,不能suspend。

3. 在定时器超时函数中调用workqueue, 然后在workqueue出处理按键事件,释放wake events
static void gpio_keys_gpio_work_func(struct work_struct *work)
{
    gpio_keys_gpio_report_event(bdata);

	if (bdata->button->wakeup)
		pm_relax(bdata->input->dev.parent);

     ...
}
只不过了每个驱动的调用接口不同罢了。


  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值