前言
通常新机制/事物的出现往往是解决某些问题的,同样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: 同步机制,用于访问链表时使用。
.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);
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);
...
}
只不过了每个驱动的调用接口不同罢了。