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

Linux電源管理-wakeup count

前言

在wakeup events framework小節中提到,wakeup events framwork可以解決system suspend和wakeup events之間的同步問題。而整篇下來沒有看到是如何解決同步問題的。所有本小節繼續分析wakeup events framework中的重要知識點-wakeup count。
"wakeup count"是不是很熟悉?  是的,在wakeup_source結構體中就存在"wakeup_count"成員,此成員的意思是:終止suspend的次數。而本小節的wakeup count並非此意,只是名字相同罷了。:(

實現原理

1.   在進行suspend之前,需要先獲取系統中總的wakeup event數量。 2.   將獲得的值儲存到全域性變數saved_count中。 3.   此後可能系統已經進入了suspend的流程中。這時候如果系統發生了wakeup events,就會增加wakeup event的數量。 4.   在suspend執行的過程中,會呼叫pm_wakeup_pending介面檢測系統有沒有發生wakeup event。(通過比較當前的wakeup events和之前儲存的值saved_count是否相同) 5.   如果不同,則終止系統suspend。否則繼續執行suspend流程。
那wakeup event framework是如何儲存當前系統中所有的wakeup event?  以及如何如何判斷當前是否有wake events正在處理?
通常思路:  用一個變數記錄當前系統發生的所有wakeup event,用另一個變數記錄當前是否有wake events在處理。 那Linux核心到底是如何記錄這兩個變數呢? 
linux中使用一個原子變數,高16位記錄系統所有的wakeup event總數,低16位記錄是否有wakeup events在處理中。 [cpp] view plain copy
  1. /* 
  2.  * Combined counters of registered wakeup events and wakeup events in progress.
     
  3.  * They need to be modified together atomically, so it's better to use one 
  4.  * atomic variable to hold them both. 
  5.  */  
  6. static atomic_t combined_event_count = ATOMIC_INIT(0);  
  7.   
  8. #define IN_PROGRESS_BITS    (sizeof(int) * 4)  
  9. #define MAX_IN_PROGRESS     ((1 << IN_PROGRESS_BITS) - 1)  
  10.   
  11. static void split_counters(unsigned int *cnt, unsigned int *inpr)  
  12. {  
  13.     unsigned int comb = atomic_read(&combined_event_count);  
  14.   
  15.     *cnt = (comb >> IN_PROGRESS_BITS);  
  16.     *inpr = comb & MAX_IN_PROGRESS;  
  17. }  
"registered wakeup events"代表系統自啟動以來所有的wakeup event的總數,在combined_event_count的高16位。 "wakeup event in progress"代表系統是否有wake events正在處理,在combined_event_count的低16位。
當系統有wakeup events上報時,呼叫wakeup events framework的介面active該wakeup source,然後"wakeup event in progress"加1。 [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. }  
那什麼時候"registered wakeup events"加1呢?  答案是在"wakeup event in progress"減1的時候,"registered wakeup events"加1。因為這時候一個wakeup event剛處理完畢,就代表系統已經發生過一個wakeup event。 [cpp] view plain copy
  1. /* 
  2.  * Increment the counter of registered wakeup events and decrement the 
  3.  * couter of wakeup events in progress simultaneously. 
  4.  */  
  5. cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);  
當然了此處的註釋寫的也很清楚。
那接著再用linux中的實現方法回答上述的問題:   wakeup event framework是如何儲存當前系統中所有的wakeup event?  以及如何如何判斷當前是否有wake events正在處理? 1.  獲取combined_event_count的高16位就可以知道有多少wakeup event。 2.  判斷combined_event_count的低16位是否為零,就知道有沒有wakeup event在處理。

實現流程

既然明白了上述的原理,就按照此原理一步一步分析程式碼的處理流程即可。 a.  在進行suspend操作之前,需要獲取wakeup event的數量。之前說過wakeup event的數量存在combined_event_count的高16位,而獲取該值可以通過split_counters介面,所以可以直接搜尋該介面即可。 [cpp] view plain copy
  1. bool pm_get_wakeup_count(unsigned int *count, bool block)  
  2. {  
  3.     unsigned int cnt, inpr;  
  4.   
  5.     if (block) {  
  6.         DEFINE_WAIT(wait);  
  7.   
  8.         for (;;) {  
  9.             prepare_to_wait(&wakeup_count_wait_queue, &wait,  
  10.                     TASK_INTERRUPTIBLE);  
  11.             split_counters(&cnt, &inpr);  
  12.             if (inpr == 0 || signal_pending(current))  
  13.                 break;  
  14.   
  15.             schedule();  
  16.         }  
  17.         finish_wait(&wakeup_count_wait_queue, &wait);  
  18.     }  
  19.   
  20.     split_counters(&cnt, &inpr);  
  21.     *count = cnt;  
  22.     return !inpr;  
  23. }  
1. 如果block為false, 直接通過split_counters就可以獲取wakeup event的總數,存在count返回。同時返回"wakeup event in progress"的狀態,如果返回false,說明有wakeup events在處理,則不允許suspend,否則可以。 2. 如果block為ture,就會定義一個wait佇列,等待"wakeup in event progress"為0,然後在返回count。
b.  獲得到當前的"wakeup event"總數後,就需要將此值存到全域性變數saved_count中。 [cpp] view plain copy
  1. bool pm_save_wakeup_count(unsigned int count)  
  2. {  
  3.     unsigned int cnt, inpr;  
  4.     unsigned long flags;  
  5.   
  6.     events_check_enabled = false;  
  7.     spin_lock_irqsave(&events_lock, flags);  
  8.     split_counters(&cnt, &inpr);  
  9.     if (cnt == count && inpr == 0) {  
  10.         saved_count = count;  
  11.         events_check_enabled = true;  
  12.     }  
  13.     spin_unlock_irqrestore(&events_lock, flags);  
  14.     return events_check_enabled;  
  15. }  
1. 首先獲取"wakeup event in progress"和"register wakeup event"的值。 2.  然後比較傳入的count是否等於"register wakeup event" 同時"wakeup event in progress"需要等於0。如果都不滿足,說明在儲存之前發生了wakeup event。 3.  置位events_check_enabled的值為true。如果此值為false,wakeup event 檢測機制就會不起作用的。
c. 假設在suspend的過程中,發生了wakeup event事件。同時上報到wakeup event framework。 d. 在susupend的流程中,就會呼叫pm_wakeup_pending介面檢測是否有wakeup event發生。比如如下程式碼: [cpp] view plain copy
  1. error = syscore_suspend();  
  2. if (!error) {  
  3.     *wakeup = pm_wakeup_pending();  
  4.     if (!(suspend_test(TEST_CORE) || *wakeup)) {  
  5.         trace_suspend_resume(TPS("machine_suspend"),  
  6.             state, true);  
  7.         error = suspend_ops->enter(state);  
  8.         trace_suspend_resume(TPS("machine_suspend"),  
  9.             state, false);  
  10.         events_check_enabled = false;  
  11.     } else if (*wakeup) {  
  12.         pm_get_active_wakeup_sources(suspend_abort,  
  13.             MAX_SUSPEND_ABORT_LEN);  
  14.         log_suspend_abort_reason(suspend_abort);  
  15.         error = -EBUSY;  
  16.     }  
  17.     syscore_resume();  
  18. }  
在suspend的最後階段會再次呼叫pending介面檢測是否有wakeup event發生的。 [cpp] view plain copy
  1. bool pm_wakeup_pending(void)  
  2. {  
  3.     unsigned long flags;  
  4.     bool ret = false;  
  5.   
  6.     spin_lock_irqsave(&events_lock, flags);  
  7.     if (events_check_enabled) {  
  8.         unsigned int cnt, inpr;  
  9.   
  10.         split_counters(&cnt, &inpr);  
  11.         ret = (cnt != saved_count || inpr > 0);  
  12.         events_check_enabled = !ret;  
  13.     }  
  14.     spin_unlock_irqrestore(&events_lock, flags);  
  15.   
  16.     if (ret) {  
  17.         pr_info("PM: Wakeup pending, aborting suspend\n");  
  18.         pm_print_active_wakeup_sources();  
  19.     }  
  20.   
  21.     return ret || pm_abort_suspend;  
  22. }  
1.  判斷events_check_enabled是否為true,如果為false,就不會abort suspend流程。 2.  如果events_check_enabled為true,獲取"registered wakeup event"和"wakeup event in progress"的值。判斷registered wakeup event的值是否和saved_count的值不等,且wakeup event in progress大於0,說明有新的wakeup event發生,不能suspend,清除event_check_enabled標誌。 3.  否則registered wakeup event等於saved_count且wakeup event in progress等於0,說明沒有新的wakeup event發生,繼續睡眠。