Linux電源管理(8)_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(請參考下一篇文章)提供介面。
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吧~~~~