Linux電源管理-wakeup events framework
阿新 • • 發佈:2019-01-09
前言
通常新機制/事物的出現往往是解決某些問題的,同樣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的裝置。
[cpp] view plain copy
- /**
- * 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;
- };
.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中就應該有某種標誌代表此裝置是否具有喚醒的能力。 [cpp] view plain copy
- 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"檔案,此檔案記錄了系統的喚醒源的詳細資訊。 [cpp] view plain copy- 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;
- }
- [email protected]:/ # 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
相關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);
- }
- /*
- * 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);
- }
- 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);
- }
- 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);
- }
- 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);
- }
- 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);
- }
- 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;
- }
- 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;
- }
- 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;
-