Linux電源管理_Wakeup count功能--(三)

1. 前言

Wakeup count是Wakeup events framework的組成部分,用於解決“system suspend和system wakeup events之間的同步問題”。本文將結合“Linux電源管理(6)_Generic PM之Suspend功能”和“Linux電源管理(7)_Wakeup events framework”兩篇文章,分析wakeup count的功能、實現邏輯、背後的思考,同時也是對這兩篇文章的複習和總結。

2. wakeup count在電源管理中的位置

wakeup count的實現位於wakeup events framework中(drivers/base/power/wakeup.c),主要爲兩個模塊提供接口:通過PM core向用戶空間提供sysfs接口;直接向autosleep(請參考下一篇文章)提供接口。

wakeup count architecture

3. wakeup count的功能

wakeup count的功能是suspend同步,實現思路是這樣的:

1)任何想發起電源狀態切換的實體(可以是用戶空間電源管理進程,也可以是內核線程,簡稱C),在發起狀態切換前,讀取系統的wakeup counts(該值記錄了當前的wakeup event總數),並將讀取的counts告知wakeup events framework。

2)wakeup events framework記錄該counts到一個全局變量中(saved_count)。

3)隨後C發起電源狀態切換(如STR),執行suspend過程。

4)在suspend的過程中,wakeup events framework照舊工作(直到系統中斷被關閉),上報wakeup events,增加wakeup events counts。

5)suspend執行的一些時間點(可參考“Linux電源管理(6)_Generic PM之Suspend功能”),會調用wakeup  events framework提供的接口(pm_wakeup_pending),檢查是否有wakeup沒有處理。

6)檢查邏輯很簡單,就是比較當前的wakeup counts和saved wakeup counts(C發起電源狀態切換時的counts),如果不同,就要終止suspend過程。

4. wakeup count的實現邏輯

4.1 一個例子

在進行代碼分析之前,我們先用僞代碼的形式,寫出一個利用wakeup count進行suspend操作的例子,然後基於該例子,分析相關的實現。

   1: do {
   2:     ret = read(&cnt, "/sys/power/wakeup_count");
   3:     if (ret) {
   4:         ret = write(cnt, "/sys/power/wakeup_count");
   5:     } else {
   6:         countine;
   7:     }
   8: } while (!ret);
   9:  
  10: write("mem", "/sys/power/state");
  11:  
  12: /* goto here after wakeup */

例子很簡單:

a)讀取wakeup count值,如果成功,將讀取的值回寫。否則說明有正在處理的wakeup events,continue。

b)回寫後,判斷返回值是否成功,如果不成功(說明讀、寫的過程中產生了wakeup events),繼續讀、寫,直到成功。成功後,可以觸發電源狀態切換。

4.2 /sys/power/wakeup_count

wakeup_count文件是在kernel/power/main.c中,利用power_attr註冊的,如下(大家可以仔細研讀一下那一大段註釋,內核很多註釋寫的非常好,而好的註釋,就是軟件功力的體現):

   1: /*
   2:  * The 'wakeup_count' attribute, along with the functions defined in
   3:  * drivers/base/power/wakeup.c, provides a means by which wakeup events can be
   4:  * handled in a non-racy way.
   5:  *
   6:  * If a wakeup event occurs when the system is in a sleep state, it simply is
   7:  * woken up.  In turn, if an event that would wake the system up from a sleep
   8:  * state occurs when it is undergoing a transition to that sleep state, the
   9:  * transition should be aborted.  Moreover, if such an event occurs when the
  10:  * system is in the working state, an attempt to start a transition to the
  11:  * given sleep state should fail during certain period after the detection of
  12:  * the event.  Using the 'state' attribute alone is not sufficient to satisfy
  13:  * these requirements, because a wakeup event may occur exactly when 'state'
  14:  * is being written to and may be delivered to user space right before it is
  15:  * frozen, so the event will remain only partially processed until the system is
  16:  * woken up by another event.  In particular, it won't cause the transition to
  17:  * a sleep state to be aborted.
  18:  *
  19:  * This difficulty may be overcome if user space uses 'wakeup_count' before
  20:  * writing to 'state'.  It first should read from 'wakeup_count' and store
  21:  * the read value.  Then, after carrying out its own preparations for the system
  22:  * transition to a sleep state, it should write the stored value to
  23:  * 'wakeup_count'.  If that fails, at least one wakeup event has occurred since
  24:  * 'wakeup_count' was read and 'state' should not be written to.  Otherwise, it
  25:  * is allowed to write to 'state', but the transition will be aborted if there
  26:  * are any wakeup events detected after 'wakeup_count' was written to.
  27:  */
  28:  
  29: static ssize_t wakeup_count_show(struct kobject *kobj,
  30:                                 struct kobj_attribute *attr,
  31:                                 char *buf)
  32: {
  33:         unsigned int val;
  34:  
  35:         return pm_get_wakeup_count(&val, true) ?
  36:                 sprintf(buf, "%u\n", val) : -EINTR;
  37: }
  38:  
  39: static ssize_t wakeup_count_store(struct kobject *kobj,
  40:                                 struct kobj_attribute *attr,
  41:                                 const char *buf, size_t n)
  42: {
  43:         unsigned int val;
  44: int error;
  45:  
  46: error = pm_autosleep_lock();
  47: if (error)
  48:         return error;
  49:  
  50: if (pm_autosleep_state() > PM_SUSPEND_ON) {
  51:         error = -EBUSY;
  52:         goto out;
  53: }
  54:  
  55: error = -EINVAL;
  56: if (sscanf(buf, "%u", &val) == 1) {
  57:         if (pm_save_wakeup_count(val))
  58:                 error = n;
  59: }
  60:  
  61:  out:
  62: pm_autosleep_unlock();
  63: return error;
  64: }
  65:  
  66: tr(wakeup_count);

實現很簡單:read時,直接調用pm_get_wakeup_count(注意第2個參數);write時,直接調用pm_save_wakeup_count(注意用戶空間的wakeup count功能和auto sleep互斥,會在下篇文章解釋原因)。這兩個接口均是wakeup events framework提供的接口,跟着代碼往下看吧。

4.3 pm_get_wakeup_count

pm_get_wakeup_count的實現如下:

   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: }

該接口有兩個參數,一個是保存返回的count值得指針,另一個指示是否block,具體請參考代碼邏輯:

a)如果block爲false,直接讀取registered wakeup events和wakeup events in progress兩個counter值,將registered wakeup events交給第一個參數,並返回wakeup events in progress的狀態(若返回false,說明當前有wakeup events正在處理,不適合suspend)。

b)如果block爲true,定義一個等待隊列,等待wakeup events in progress爲0,再返回counter。

 

注1:由4.2小節可知,sysfs發起的read動作,block爲true,所以如果有正在處理的wakeup events,read進程會阻塞。其它模塊(如auto sleep)發起的read,則可能不需要阻塞。

4.4 pm_save_wakeup_count

pm_save_wakeup_count的實現如下:

   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)注意這個變量,events_check_enabled,如果它不爲真,pm_wakeup_pending接口直接返回false,意味着如果不利用wakeup count功能,suspend過程中不會做任何wakeup events檢查,也就不會進行任何的同步

2)解除當前的registered wakeup events、wakeup events in progress,保存在變量cnt和inpr中。

3)如果寫入的值和cnt不同(說明讀、寫的過程中產生events),或者inpr不爲零(說明有events正在被處理),返回false(說明此時不宜suspend)。

4)否則,events_check_enabled置位(後續的pm_wakeup_pending纔會幹活),返回true(可以suspend),並將當前的wakeup count保存在saved count變量中。

4.5 /sys/power/state

再回憶一下“Linux電源管理(6)_Generic PM之Suspend功能”中suspend的流程,在suspend_enter接口中,suspend前的最後一刻,會調用pm_wakeup_pending接口,代碼如下:

   1: static int suspend_enter(suspend_state_t state, bool *wakeup)
   2: {
   3:     ...
   4:     error = syscore_suspend();
   5:     if (!error) {
   6:         *wakeup = pm_wakeup_pending();
   7:         if (!(suspend_test(TEST_CORE) || *wakeup)) {
   8:                 error = suspend_ops->enter(state);
   9:                 events_check_enabled = false;
  10:         }
  11:         syscore_resume();
  12:     }
  13:     ...
  14: }

在write wakeup_count到調用pm_wakeup_pending這一段時間內,wakeup events framework會照常產生wakeup events,因此如果pending返回true,則不能“enter”,終止suspend吧!

注2:wakeup後,會清除events_check_enabled標記。

Linux電源管理(7)_Wakeup events framework”中已經介紹過pm_wakeup_pending了,讓我們再看一遍吧:

   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:                 print_active_wakeup_sources();
  18:  
  19:         return ret;
  20: }

a)首先會判斷events_check_enabled是否有效,無效直接返回false。有效的話:

b)獲得cnt和inpr,如果cnt不等於saved_count(說明這段時間內有events產生),或者inpr不爲0(說明有events正在被處理),返回true(告訴調用者,放棄吧,時機不到)。同時清除events_check_enabled的狀態。

c)否則,返回false(放心睡吧),同時保持events_check_enabled的置位狀態(以免pm_wakeup_pending再次調用)。

Okay,結束了,等待wakeup吧~~~~

 

原創文章,轉發請註明出處。蝸窩科技,www.wowotech.net


debug方式

1. 先確認一下您的系統是否使能了trace功能(默認情況下都會使能)。
2. 如果使能,會存在“/sys/kernel/debug/tracing/”目錄。
3. echo 1 > /sys/kernel/debug/tracing/events/power/wakeup_source_activate/enable,使能wakeup source activate的trace輸出。
4. cat /sys/kernel/debug/tracing/trace,查看哪些driver一直處於activate狀態,輸出信息如下:
# entries-in-buffer/entries-written: 132/132   #P:1
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
    kworker/u8:0-28958 [000] d..2 11250.490538: wakeup_source_activate: delay_lock state=0x2f900004
    kworker/u8:0-28958 [000] d..4 11250.491522: wakeup_source_activate: atc260x-wall state=0x2f910004
    kworker/u8:0-28958 [000] d..4 11250.491553: wakeup_source_activate: atc260x-usb state=0x2f910005
     kworker/0:0-25134 [000] d..4 11250.491645: wakeup_source_activate: NETLINK state=0x2f910006
主要包括driver的名稱,和當前的“combined_event_count”的值。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章