1. 程式人生 > >Linux電源管理-wakeup events framework

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的裝置。
[cpp] view plain copy
  1. /** 
  2.  * struct wakeup_source - Representation of wakeup sources
     
  3.  * 
  4.  * @total_time: Total time this wakeup source has been active. 
  5.  * @max_time: Maximum time this wakeup source has been continuously active. 
  6.  * @last_time: Monotonic clock when the wakeup source's was touched last time.
     
  7.  * @prevent_sleep_time: Total time this source has been preventing autosleep. 
  8.  * @event_count: Number of signaled wakeup events. 
  9.  * @active_count: Number of times the wakeup source was activated. 
  10.  * @relax_count: Number of times the wakeup source was deactivated. 
  11.  * @expire_count: Number of times the wakeup source's timeout has expired. 
  12.  * @wakeup_count: Number of times the wakeup source might abort suspend. 
  13.  * @active: Status of the wakeup source. 
  14.  * @has_timeout: The wakeup source has been activated with a timeout. 
  15.  */  
  16. struct wakeup_source {  
  17.     const char      *name;  
  18.     struct list_head    entry;  
  19.     spinlock_t      lock;  
  20.     struct timer_list   timer;  
  21.     unsigned long       timer_expires;  
  22.     ktime_t total_time;  
  23.     ktime_t max_time;  
  24.     ktime_t last_time;  
  25.     ktime_t start_prevent_time;  
  26.     ktime_t prevent_sleep_time;  
  27.     unsigned long       event_count;  
  28.     unsigned long       active_count;  
  29.     unsigned long       relax_count;  
  30.     unsigned long       expire_count;  
  31.     unsigned long       wakeup_count;  
  32.     bool            active:1;  
  33.     bool            autosleep_enabled:1;  
  34. };  
.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中就應該有某種標誌代表此裝置是否具有喚醒的能力。 [cpp] view plain copy
  1. struct device {  
  2.     ...  
  3.     struct dev_pm_info  power;   
  4.     struct dev_pm_domain    *pm_domain;  
  5.     ...  
  6. }  
其中dev_pm_info代表該裝置pm相關的詳細資訊。 [cpp] view plain copy
  1. struct dev_pm_info {  
  2.     pm_message_t        power_state;  
  3.     unsigned int        can_wakeup:1;  
  4.     unsigned int        async_suspend:1;  
  5.       
  6.     ...  
  7. #ifdef CONFIG_PM_SLEEP  
  8.     struct list_head    entry;  
  9.     struct completion   completion;  
  10.     struct wakeup_source    *wakeup;  
  11.     bool            wakeup_path:1;  
  12.     bool            syscore:1;  
  13. #else  
  14.     unsigned int        should_wakeup:1;  
  15. #endif  
  16.     ...  
  17. }  
其中can_wakeup就代表該裝置是否具有喚醒系統的能力。只有具有喚醒能力的device,在sys/devices/xxx/下就會存在power相關目錄的。

Sys介面

為了方便檢視系統的wakeup sources,linux系統在/sys/kernel/debug下建立了一個"wakeup_sources"檔案,此檔案記錄了系統的喚醒源的詳細資訊。 [cpp] view plain copy
  1. static int wakeup_sources_stats_show(struct seq_file *m, void *unused)  
  2. {  
  3.     struct wakeup_source *ws;  
  4.   
  5.     seq_puts(m, "name\t\tactive_count\tevent_count\twakeup_count\t"  
  6.         "expire_count\tactive_since\ttotal_time\tmax_time\t"  
  7.         "last_change\tprevent_suspend_time\n");  
  8.   
  9.     rcu_read_lock();  
  10.     list_for_each_entry_rcu(ws, &wakeup_sources, entry)  
  11.         print_wakeup_source_stats(m, ws);  
  12.     rcu_read_unlock();  
  13.   
  14.     return 0;  
  15. }  
  16.   
  17. static int wakeup_sources_stats_open(struct inode *inode, struct file *file)  
  18. {  
  19.     return single_open(file, wakeup_sources_stats_show, NULL);  
  20. }  
  21.   
  22. static const struct file_operations wakeup_sources_stats_fops = {  
  23.     .owner = THIS_MODULE,  
  24.     .open = wakeup_sources_stats_open,  
  25.     .read = seq_read,  
  26.     .llseek = seq_lseek,  
  27.     .release = single_release,  
  28. };  
  29.   
  30. static int __init wakeup_sources_debugfs_init(void)  
  31. {  
  32.     wakeup_sources_stats_dentry = debugfs_create_file("wakeup_sources",  
  33.             S_IRUGO, NULL, NULL, &wakeup_sources_stats_fops);  
  34.     return 0;  
  35. }  
以下是手機上的wakeup sources資訊 [cpp] view plain copy
  1. [email protected]:/ # cat /sys/kernel/debug/wakeup_sources                      
  2. name        active_count    event_count wakeup_count    expire_count    active_since    total_time  max_time    last_change prevent_suspend_time  
  3. event1          40644       40644       0       0       0       31294       30      537054822       0  
  4. event4          4496        4496        0       0       0       13369       22      20913677        0  
  5. event5          4496        4496        0       0       0       13048       22      20913677        0  
  6. event0          4540        4540        0       0       0       27995       277     258270184       0  
  7. eventpoll       40688       54176       0       0       0       217     5       537054822       0  
  8. 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)
[cpp] view plain copy
  1. void pm_stay_awake(struct device *dev)  
  2. {  
  3.     unsigned long flags;  
  4.   
  5.     if (!dev)  
  6.         return;  
  7.   
  8.     spin_lock_irqsave(&dev->power.lock, flags);  
  9.     __pm_stay_awake(dev->power.wakeup);  
  10.     spin_unlock_irqrestore(&dev->power.lock, flags);  
該函式直接就呼叫__pm_stay_awake函式。此函式可以在中斷上下文使用。 [cpp] view plain copy
  1. void __pm_stay_awake(struct wakeup_source *ws)  
  2. {  
  3.     unsigned long flags;  
  4.   
  5.     if (!ws)  
  6.         return;  
  7.   
  8.     spin_lock_irqsave(&ws->lock, flags);  
  9.   
  10.     wakeup_source_report_event(ws);  
  11.     del_timer(&ws->timer);  
  12.     ws->timer_expires = 0;  
  13.   
  14.     spin_unlock_irqrestore(&ws->lock, flags);  
  15. }  
當wakeup source產生wakup events之後,呼叫pm_stay_awake函式上報wakeup events。隨後會呼叫pm_relax函式,通知PM core wakeup events已經處理完畢。所以在__pm_stay_awake中不需要定時器。隨後呼叫wakeup_source_report_event上報wakup events。 [cpp] view plain copy
  1. static void wakeup_source_report_event(struct wakeup_source *ws)  
  2. {  
  3.     ws->event_count++;  
  4.     /* This is racy, but the counter is approximate anyway. */  
  5.     if (events_check_enabled)  
  6.         ws->wakeup_count++;  
  7.   
  8.     if (!ws->active)  
  9.         wakeup_source_activate(ws);  
  10. }  
1.  wakeup events個數加1,也就是event_count加1。 2.  如果events_check_enabled設定了,則會終止系統suspend/hibernate,此時就需要將wakup_count加1, 代表阻止了suspend的次數。 [cpp] view plain copy
  1. /* 
  2.  * If set, the suspend/hibernate code will abort transitions to a sleep state 
  3.  * if wakeup events are registered during or immediately before the transition. 
  4.  */  
  5. bool events_check_enabled __read_mostly;  
3. 如果wakup source沒有啟用的,啟用該wakup source。假如已經處於active狀態,則event_count就比active_count大。 [cpp] view plain copy
  1. static void wakeup_source_activate(struct wakeup_source *ws)  
  2. {  
  3.     unsigned int cec;  
  4.   
  5.     /* 
  6.      * active wakeup source should bring the system 
  7.      * out of PM_SUSPEND_FREEZE state 
  8.      */  
  9.     freeze_wake();  
  10.   
  11.     ws->active = true;  
  12.     ws->active_count++;  
  13.     ws->last_time = ktime_get();  
  14.     if (ws->autosleep_enabled)  
  15.         ws->start_prevent_time = ws->last_time;  
  16.   
  17.     /* Increment the counter of events in progress. */  
  18.     cec = atomic_inc_return(&combined_event_count);  
  19.   
  20.     trace_wakeup_source_activate(ws->name, cec);  
  21. }  
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)
[cpp] view plain copy
  1. void pm_relax(struct device *dev)  
  2. {  
  3.     unsigned long flags;  
  4.   
  5.     if (!dev)  
  6.         return;  
  7.   
  8.     spin_lock_irqsave(&dev->power.lock, flags);  
  9.     __pm_relax(dev->power.wakeup);  
  10.     spin_unlock_irqrestore(&dev->power.lock, flags);  
  11. }  
該函式也是直接呼叫__pm_relax函式。 [cpp] view plain copy
  1. void __pm_relax(struct wakeup_source *ws)  
  2. {  
  3.     unsigned long flags;  
  4.   
  5.     if (!ws)  
  6.         return;  
  7.   
  8.     spin_lock_irqsave(&ws->lock, flags);  
  9.     if (ws->active)  
  10.         wakeup_source_deactivate(ws);  
  11.     spin_unlock_irqrestore(&ws->lock, flags);  
  12. }  
如果該wakeup source已經處於active狀態,則呼叫wakeup_source_deactivate函式deactivce之。 [cpp] view plain copy
  1. static void wakeup_source_deactivate(struct wakeup_source *ws)  
  2. {  
  3.     unsigned int cnt, inpr, cec;  
  4.     ktime_t duration;  
  5.     ktime_t now;  
  6.   
  7.     ws->relax_count++;  
  8.     /* 
  9.      * __pm_relax() may be called directly or from a timer function. 
  10.      * If it is called directly right after the timer function has been 
  11.      * started, but before the timer function calls __pm_relax(), it is 
  12.      * possible that __pm_stay_awake() will be called in the meantime and 
  13.      * will set ws->active.  Then, ws->active may be cleared immediately 
  14.      * by the __pm_relax() called from the timer function, but in such a 
  15.      * case ws->relax_count will be different from ws->active_count. 
  16.      */  
  17.     if (ws->relax_count != ws->active_count) {  
  18.         ws->relax_count--;  
  19.         return;  
  20.     }  
  21.   
  22.     ws->active = false;  
  23.   
  24.     now = ktime_get();  
  25.     duration = ktime_sub(now, ws->last_time);  
  26.     ws->total_time = ktime_add(ws->total_time, duration);  
  27.     if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time))  
  28.         ws->max_time = duration;  
  29.   
  30.     ws->last_time = now;  
  31.     del_timer(&ws->timer);  
  32.     ws->timer_expires = 0;  
  33.   
  34.     if (ws->autosleep_enabled)  
  35.         update_prevent_sleep_time(ws, now);  
  36.   
  37.     /* 
  38.      * Increment the counter of registered wakeup events and decrement the 
  39.      * couter of wakeup events in progress simultaneously. 
  40.      */  
  41.     cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);  
  42.     trace_wakeup_source_deactivate(ws->name, cec);  
  43.   
  44.     split_counters(&cnt, &inpr);  
  45.     if (!inpr && waitqueue_active(&wakeup_count_wait_queue))  
  46.         wake_up(&wakeup_count_wait_queue);  
  47. }  
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 [cpp] view plain copy
  1. static void update_prevent_sleep_time(struct wakeup_source *ws, ktime_t now)  
  2. {  
  3.     ktime_t delta = ktime_sub(now, ws->start_prevent_time);  
  4.     ws->prevent_sleep_time = ktime_add(ws->prevent_sleep_time, delta);  
  5. }  
5.  增加"registered wakeup events"同時減少“wakeup events in progress”。 6.  wakeup count相關的處理,留到wakeup count小節分析。總之簡單理解就是啟用active的反操作。
  • device_init_wakeup(wakeup source初始化操作,通常在裝置驅動中使用該介面)
[cpp] view plain copy
  1. int device_init_wakeup(struct device *dev, bool enable)  
  2. {  
  3.     int ret = 0;  
  4.   
  5.     if (!dev)  
  6.         return -EINVAL;  
  7.   
  8.     if (enable) {  
  9.         device_set_wakeup_capable(dev, true);  
  10.         ret = device_wakeup_enable(dev);  
  11.     } else {  
  12.         if (dev->power.can_wakeup)  
  13.             device_wakeup_disable(dev);  
  14.   
  15.         device_set_wakeup_capable(dev, false);  
  16.     }  
  17.   
  18.     return ret;  
  19. }  
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喚醒的能力)
[cpp] view plain copy
  1. void device_set_wakeup_capable(struct device *dev, bool capable)  
  2. {  
  3.     if (!!dev->power.can_wakeup == !!capable)  
  4.         return;  
  5.   
  6.     if (device_is_registered(dev) && !list_empty(&dev->power.entry)) {  
  7.         if (capable) {  
  8.             if (wakeup_sysfs_add(dev))  
  9.                 return;  
  10.         } else {  
  11.             wakeup_sysfs_remove(dev);  
  12.         }  
  13.     }  
  14.     dev->power.can_wakeup = capable;  
  15. }  
如果capable等於ture, 則設定power.can_wakup的flag, 然後新增wakup屬性到sys中。如果capable等於false,將wakeup屬性從sys中移除,然後設定can_wakeup屬性。wakup屬性定義如下: [cpp] view plain copy
  1. static struct attribute *wakeup_attrs[] = {  
  2. #ifdef CONFIG_PM_SLEEP  
  3.     &dev_attr_wakeup.attr,  
  4.     &dev_attr_wakeup_count.attr,  
  5.     &dev_attr_wakeup_active_count.attr,  
  6.     &dev_attr_wakeup_abort_count.attr,  
  7.     &dev_attr_wakeup_expire_count.attr,  
  8.     &dev_attr_wakeup_active.attr,  
  9.     &dev_attr_wakeup_total_time_ms.attr,  
  10.     &dev_attr_wakeup_max_time_ms.attr,  
  11.     &dev_attr_wakeup_last_time_ms.attr,  
  12. #ifdef CONFIG_PM_AUTOSLEEP  
  13.     &dev_attr_wakeup_prevent_sleep_time_ms.attr,  
  14. #endif  
  15. #endif  
  16.     NULL,  
  17. };  
  18. static struct attribute_group pm_wakeup_attr_group = {  
  19.     .name   = power_group_name,  
  20.     .attrs  = wakeup_attrs,  
  21. };  
關於讀取/設定wakeup的屬性這裡不再詳細說明,前面已經見過很多次了。
  • device_wakeup_enable(enable device to be a wakeup source)
[cpp] view plain copy
  1. int device_wakeup_enable(struct device *dev)  
  2. {  
  3.     struct wakeup_source *ws;  
  4.     int ret;  
  5.   
  6.     if (!dev || !dev->power.can_wakeup)  
  7.         return -EINVAL;  
  8.   
  9.     ws = wakeup_source_register(dev_name(dev));  
  10.     if (!ws)  
  11.         return -ENOMEM;  
  12.   
  13.     ret = device_wakeup_attach(dev, ws);  
  14.     if (ret)  
  15.         wakeup_source_unregister(ws);  
  16.   
  17.     return ret;  
  18. }  
1.  如果device不存在或者device不具有wakeup能力,則返回-EINVAL。 2.  建立wakeup source。註冊wakeup source。 3.  將裝置和wakeup source建立連線。如果失敗,則釋放wakeup source。
  • wakeup_source_register(分配一個喚醒源,將其加入到wakeup source連結串列中)
[cpp] view plain copy
  1. struct wakeup_source *wakeup_source_register(const char *name)  
  2. {  
  3.     struct wakeup_source *ws;  
  4.   
  5.     ws = wakeup_source_create(name);  
  6.     if (ws)  
  7.         wakeup_source_add(ws);  
  8.   
  9.     return ws;  
  10. }  
1.  分配一個wakeup_source結構體,然後設定該wakeup source的name域。 [cpp] view plain copy
  1. struct wakeup_source *wakeup_source_create(const char *name)  
  2. {  
  3.     struct wakeup_source *ws;  
  4.   
  5.     ws = kmalloc(sizeof(*ws), GFP_KERNEL);  
  6.     if (!ws)  
  7.         return NULL;  
  8.   
  9.     wakeup_source_prepare(ws, name ? kstrdup(name, GFP_KERNEL) : NULL);  
  10.     return ws;  
  11. }  
2. 新增給定的wakeup source到wakup source連結串列中。 [cpp] view plain copy
  1. void wakeup_source_add(struct wakeup_source *ws)  
  2. {  
  3.     unsigned long flags;  
  4.   
  5.