前言
通常新机制/事物的出现往往是解决某些问题的,同样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;
- };
.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;
- ...
- }
- 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
- ...
- }
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;
- }
- 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
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);
- 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);
- }
- 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);
- }
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;
- 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);
- }
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);
- }
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);
- }
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;
- }
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;
- }
- 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,
- };
- 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;
- }
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;
- }
- 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;
- }
- 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);
- }
次数,如果定时器设置的时间超时,则会调用定时器超时函数,在超时函数中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;
- }
- 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 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;
- }
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);
- ...
- }