Linux電源管理-wakeup count
阿新 • • 發佈:2019-01-09
前言
在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正在處理?
linux中使用一個原子變數,高16位記錄系統所有的wakeup event總數,低16位記錄是否有wakeup events在處理中。 [cpp] view plain copy
- /*
- * Combined counters of registered wakeup events and wakeup events in progress.
- * They need to be modified together atomically, so it's better to use one
- * atomic variable to hold them both.
- */
- static atomic_t combined_event_count = ATOMIC_INIT(0);
- #define IN_PROGRESS_BITS (sizeof(int) * 4)
- #define MAX_IN_PROGRESS ((1 << IN_PROGRESS_BITS) - 1)
- static void split_counters(unsigned int *cnt, unsigned int *inpr)
- {
- unsigned int comb = atomic_read(&combined_event_count);
- *cnt = (comb >> IN_PROGRESS_BITS);
- *inpr = comb & MAX_IN_PROGRESS;
- }
當系統有wakeup events上報時,呼叫wakeup events framework的介面active該wakeup source,然後"wakeup event in progress"加1。 [cpp] view plain copy
- 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);
- }
- /*
- * 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);
那接著再用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- bool pm_get_wakeup_count(unsigned int *count, bool block)
- {
- unsigned int cnt, inpr;
- if (block) {
- DEFINE_WAIT(wait);
- for (;;) {
- prepare_to_wait(&wakeup_count_wait_queue, &wait,
- TASK_INTERRUPTIBLE);
- split_counters(&cnt, &inpr);
- if (inpr == 0 || signal_pending(current))
- break;
- schedule();
- }
- finish_wait(&wakeup_count_wait_queue, &wait);
- }
- split_counters(&cnt, &inpr);
- *count = cnt;
- return !inpr;
- }
b. 獲得到當前的"wakeup event"總數後,就需要將此值存到全域性變數saved_count中。 [cpp] view plain copy
- bool pm_save_wakeup_count(unsigned int count)
- {
- unsigned int cnt, inpr;
- unsigned long flags;
- events_check_enabled = false;
- spin_lock_irqsave(&events_lock, flags);
- split_counters(&cnt, &inpr);
- if (cnt == count && inpr == 0) {
- saved_count = count;
- events_check_enabled = true;
- }
- spin_unlock_irqrestore(&events_lock, flags);
- return events_check_enabled;
- }
c. 假設在suspend的過程中,發生了wakeup event事件。同時上報到wakeup event framework。 d. 在susupend的流程中,就會呼叫pm_wakeup_pending介面檢測是否有wakeup event發生。比如如下程式碼: [cpp] view plain copy
- error = syscore_suspend();
- if (!error) {
- *wakeup = pm_wakeup_pending();
- if (!(suspend_test(TEST_CORE) || *wakeup)) {
- trace_suspend_resume(TPS("machine_suspend"),
- state, true);
- error = suspend_ops->enter(state);
- trace_suspend_resume(TPS("machine_suspend"),
- state, false);
- events_check_enabled = false;
- } else if (*wakeup) {
- pm_get_active_wakeup_sources(suspend_abort,
- MAX_SUSPEND_ABORT_LEN);
- log_suspend_abort_reason(suspend_abort);
- error = -EBUSY;
- }
- syscore_resume();
- }
- bool pm_wakeup_pending(void)
- {
- unsigned long flags;
- bool ret = false;
- spin_lock_irqsave(&events_lock, flags);
- if (events_check_enabled) {
- unsigned int cnt, inpr;
- split_counters(&cnt, &inpr);
- ret = (cnt != saved_count || inpr > 0);
- events_check_enabled = !ret;
- }
- spin_unlock_irqrestore(&events_lock, flags);
- if (ret) {
- pr_info("PM: Wakeup pending, aborting suspend\n");
- pm_print_active_wakeup_sources();
- }
- return ret || pm_abort_suspend;
- }