前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux电源管理-wakeup events framework

Linux电源管理-wakeup events framework

作者头像
DragonKingZhu
发布2020-03-24 14:38:41
3.8K0
发布2020-03-24 14:38:41
举报
文章被收录于专栏:Linux内核深入分析

前言

通常新机制/事物的出现往往是解决某些问题的,同样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的设备。

代码语言:javascript
复制
/**
 * 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中就应该有某种标志代表此设备是否具有唤醒的能力。

代码语言:javascript
复制
struct device {
    ...
	struct dev_pm_info	power; 
	struct dev_pm_domain	*pm_domain;
	...
}

其中dev_pm_info代表该设备pm相关的详细信息。

代码语言:javascript
复制
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"文件,此文件记录了系统的唤醒源的详细信息。

代码语言:javascript
复制
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信息

代码语言:javascript
复制
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)
代码语言:javascript
复制
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函数。此函数可以在中断上下文使用。

代码语言:javascript
复制
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。

代码语言:javascript
复制
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的次数。

代码语言:javascript
复制
/*
 * 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大。

代码语言:javascript
复制
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)
代码语言:javascript
复制
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函数。

代码语言:javascript
复制
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之。

代码语言:javascript
复制
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

代码语言:javascript
复制
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初始化操作,通常在设备驱动中使用该接口)
代码语言:javascript
复制
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唤醒的能力)
代码语言:javascript
复制
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属性定义如下:

代码语言:javascript
复制
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)
代码语言:javascript
复制
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链表中)
代码语言:javascript
复制
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域。

代码语言:javascript
复制
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链表中。

代码语言:javascript
复制
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。

代码语言:javascript
复制
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建立连接)
代码语言:javascript
复制
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, 在一段时间之后取消唤醒源)
代码语言:javascript
复制
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

代码语言:javascript
复制
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的中断处理函数。

代码语言:javascript
复制
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

代码语言:javascript
复制
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);

     ...
}

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 数据结构
  • Sys接口
  • 相关API
  • 示例分析
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档