Linux電源管理(4)_Power Management Interface
1. 前言
Linux電源管理中,相當多的部分是在處理Hibernate、Suspend、Runtime PM等功能。而這些功能都基於一套相似的邏輯,即“Power management interface”。該Interface的程式碼實現於“include/linux/pm.h”、“drivers/base/power/main.c”等檔案中。主要功能是:對下,定義Device PM相關的回撥函式,讓各個Driver實現;對上,實現統一的PM操作函式,供PM核心邏輯呼叫。
因此在對Hibernate、Suspend、Runtime PM等功能解析之前,有必要先熟悉一下PM Interface,這就是本文的主要目的。
2. device PM callbacks
在一個系統中,數量最多的是裝置,耗電最多的也是裝置,因此裝置的電源管理是Linux電源管理的核心內容。而裝置電源管理最核心的操作就是:在合適的時機(如不再使用,如暫停使用),將裝置置為合理的狀態(如關閉,如睡眠)。這就是device PM callbacks的目的:定義一套統一的方式,讓裝置在特定的時機,步調一致的進入類似的狀態(可以想象一下軍訓時的“一二一”口令)。
在舊版本的核心中,這些PM callbacks分佈在裝置模型的大型資料結構中,如struct bus_type中的suspend、suspend_late、resume、resume_late,如struct device_driver/struct class/struct device_type中的suspend、resume。很顯然這樣不具備良好的封裝特性,因為隨著裝置複雜度的增加,簡單的suspend、resume已經不能滿足電源管理的需求,就需要擴充PM callbacks,就會不可避免的改動這些資料結構。
於是新版本的核心,就將這些Callbacks統一封裝為一個數據結構----struct dev_pm_ops,上層的資料結構只需要包含這個結構即可。這樣如果需要增加或者修改PM callbacks,就不用改動上層結構了(這就是軟體設計中抽象和封裝的生動體現,像藝術一樣優雅)。當然,核心為了相容舊的設計,也保留了上述的suspend/resume型別的callbacks,只是已不建議使用,本文就不再介紹它們了。
相信每一個熟悉了舊版本核心的Linux工程師,看到struct dev_pm_ops時都會虎軀一震,這玩意也太複雜了吧!不信您請看:
1: /* include/linux/pm.h, line 276 in linux-3.10.29 */
2: struct dev_pm_ops {
3: int (*prepare)(struct device *dev);
4: void (*complete)(struct device *dev);
5: int (*suspend)(struct device *dev);
6: int (*resume)(struct device *dev);
7: int (*freeze)(struct device *dev);
8: int (*thaw)(struct device *dev);
9: int (*poweroff)(struct device *dev);
10: int (*restore)(struct device *dev);
11: int (*suspend_late)(struct device *dev);
12: int (*resume_early)(struct device *dev);
13: int (*freeze_late)(struct device *dev);
14: int (*thaw_early)(struct device *dev);
15: int (*poweroff_late)(struct device *dev);
16: int (*restore_early)(struct device *dev);
17: int (*suspend_noirq)(struct device *dev);
18: int (*resume_noirq)(struct device *dev);
19: int (*freeze_noirq)(struct device *dev);
20: int (*thaw_noirq)(struct device *dev);
21: int (*poweroff_noirq)(struct device *dev);
22: int (*restore_noirq)(struct device *dev);
23: int (*runtime_suspend)(struct device *dev);
24: int (*runtime_resume)(struct device *dev);
25: int (*runtime_idle)(struct device *dev);
26: };
從Linux PM Core的角度來說,這些callbacks並不複雜,因為PM Core要做的就是在特定的電源管理階段,呼叫相應的callbacks,例如在suspend/resume的過程中,PM Core會依次呼叫“prepare—>suspend—>suspend_late—>suspend_noirq-------wakeup--------->resume_noirq—>resume_early—>resume-->complete”。
但由於這些callbacks需要由具體的裝置Driver實現,這就要求驅動工程師在設計每個Driver時,清晰的知道這些callbacks的使用場景、是否需要實現、怎麼實現,這才是struct dev_pm_ops的複雜之處。
Linux kernel對struct dev_pm_ops的註釋已經非常詳細了,但要弄清楚每個callback的使用場景、背後的思考,並不是一件容易的事情。因此蝸蝸不準備在本文對它們進行過多的解釋,而打算結合具體的電源管理行為,基於具體的場景,再進行解釋。
3. device PM callbacks在裝置模型中的體現
我們在介紹“Linux裝置模型”時,曾多次提及電源管理相關的內容,那時蝸蝸採取忽略的方式,暫不說明。現在是時候回過頭再去看看了。
Linux裝置模型中的很多資料結構,都會包含struct dev_pm_ops變數,具體如下:
1: struct bus_type {
2: ...
3: const struct dev_pm_ops *pm;
4: ...
5: };
6:
7: struct device_driver {
8: ...
9: const struct dev_pm_ops *pm;
10: ...
11: };
12:
13: struct class {
14: ...
15: const struct dev_pm_ops *pm;
16: ...
17: };
18:
19: struct device_type {
20: ...
21: const struct dev_pm_ops *pm;
22: };
23:
24: struct device {
25: ...
26: struct dev_pm_info power;
27: struct dev_pm_domain *pm_domain;
28: ...
29: };
bus_type、device_driver、class、device_type等結構中的pm指標,比較容易理解,和舊的suspend/resume callbacks類似。我們重點關注一下device結構中的power和pm_domain變數。
◆power變數
power是一個struct dev_pm_info型別的變數,也在“include/linux/pm.h”中定義。從蝸蝸一直工作於的Linux-2.6.23核心,到寫這篇文章所用的Linux-3.10.29核心,這個資料結構可是一路發展壯大,從那時的只有4個欄位,到現在有40多個欄位,簡直是想起來什麼就放什麼啊!
power變數主要儲存PM相關的狀態,如當前的power_state、是否可以被喚醒、是否已經prepare完成、是否已經suspend完成等等。由於涉及的內容非常多,我們在具體使用的時候,順便說明。
◆pm_domain指標
在當前的核心中,struct dev_pm_domain結構只包含了一個struct dev_pm_ops ops。蝸蝸猜測這是從可擴充套件性方面考慮的,後續隨著核心的進化,可能會在該結構中新增其他內容。
所謂的PM Domain(電源域),是針對“device”來說的。bus_type、device_driver、class、device_type等結構,本質上代表的是裝置驅動,電源管理的操作,由裝置驅動負責,是理所應當的。但在核心中,由於各種原因,是允許沒有driver的device存在的,那麼怎麼處理這些裝置的電源管理呢?就是通過裝置的電源域實現的。
4. device PM callbacks的操作函式
核心在定義device PM callbacks資料結構的同時,為了方便使用該資料結構,也定義了大量的操作API,這些API分為兩類。
◆通用的輔助性質的API,直接呼叫指定裝置所繫結的driver的、pm指標的、相應的callback,如下
1: extern int pm_generic_prepare(struct device *dev);
2: extern int pm_generic_suspend_late(struct device *dev);
3: extern int pm_generic_suspend_noirq(struct device *dev);
4: extern int pm_generic_suspend(struct device *dev);
5: extern int pm_generic_resume_early(struct device *dev);
6: extern int pm_generic_resume_noirq(struct device *dev);
7: extern int pm_generic_resume(struct device *dev);
8: extern int pm_generic_freeze_noirq(struct device *dev);
9: extern int pm_generic_freeze_late(struct device *dev);
10: extern int pm_generic_freeze(struct device *dev);
11: extern int pm_generic_thaw_noirq(struct device *dev);
12: extern int pm_generic_thaw_early(struct device *dev);
13: extern int pm_generic_thaw(struct device *dev);
14: extern int pm_generic_restore_noirq(struct device *dev);
15: extern int pm_generic_restore_early(struct device *dev);
16: extern int pm_generic_restore(struct device *dev);
17: extern int pm_generic_poweroff_noirq(struct device *dev);
18: extern int pm_generic_poweroff_late(struct device *dev);
19: extern int pm_generic_poweroff(struct device *dev);
20: extern void pm_generic_complete(struct device *dev);
以pm_generic_prepare為例,就是檢視dev->driver->pm->prepare介面是否存在,如果存在,直接呼叫並返回結果。
◆和整體電源管理行為相關的API,目的是將各個獨立的電源管理行為組合起來,組成一個較為簡單的功能,如下
1: #ifdef CONFIG_PM_SLEEP
2: extern void device_pm_lock(void);
3: extern void dpm_resume_start(pm_message_t state);
4: extern void dpm_resume_end(pm_message_t state);
5: extern void dpm_resume(pm_message_t state);
6: extern void dpm_complete(pm_message_t state);
7:
8: extern void device_pm_unlock(void);
9: extern int dpm_suspend_end(pm_message_t state);
10: extern int dpm_suspend_start(pm_message_t state);
11: extern int dpm_suspend(pm_message_t state);
12: extern int dpm_prepare(pm_message_t state);
13:
14: extern void __suspend_report_result(const char *function, void *fn, int ret);
15:
16: #define suspend_report_result(fn, ret) \
17: do { \
18: __suspend_report_result(__func__, fn, ret); \
19: } while (0)
20:
21: extern int device_pm_wait_for_dev(struct device *sub, struct device *dev);
22: extern void dpm_for_each_dev(void *data, void (*fn)(struct device *, void *));
這些API的功能和動作解析如下。
dpm_prepare,執行所有裝置的“->prepare() callback(s)”,內部動作為:
1)遍歷dpm_list,依次取出掛在該list中的device指標。
【注1:裝置模型在新增裝置(device_add)時,會呼叫device_pm_add介面,將該裝置新增到全域性連結串列dpm_list中,以方便後續的遍歷操作。】2)呼叫內部介面device_prepare,執行實際的prepare動作。該介面會返回執行的結果。
3)如果執行失敗,列印錯誤資訊。
4)如果執行成功,將dev->power.is_prepared(就是上面我們提到的struct dev_pm_info型別的變數)設為TRUE,表示裝置已經prepared了。同時,將該裝置新增到dpm_prepared_list中(該連結串列儲存了所有已經處於prepared狀態的裝置)。
內部介面device_prepare的執行動作為:
1)根據dev->power.syscore,斷該裝置是否為syscore裝置。如果是,則直接返回(因為syscore裝置會單獨處理)。
2)在prepare時期,呼叫pm_runtime_get_noresume介面,關閉Runtime suspend功能。以避免由Runtime suspend造成的不能正常喚醒的Issue。該功能會在complete時被重新開啟。
【注2:pm_runtime_get_noresume的實現很簡單,就是增加該裝置power變數的引用計數(dev->power.usage_count),Runtime PM會根據該計數是否大於零,判斷是否開啟Runtime PM功能。】3)呼叫device_may_wakeup介面,根據當前裝置是否有wakeup source(dev->power.wakeup)以及是否允許wakeup(dev->power.can_wakeup),判定該裝置是否是一個wakeup path(記錄在dev->power.wakeup_path中)。
【注3:裝置的wake up功能,是指系統在低功耗狀態下(如suspend、hibernate),某些裝置具備喚醒系統的功能。這是電源管理過程的一部分。】4)根據優先順序,獲得用於prepare的callback函式。由於裝置模型有bus、driver、device等多個層級,而prepare介面可能由任意一個層級實現。這裡的優先順序是指,只要優先順序高的層級註冊了prepare,就會優先使用它,而不會使用優先順序低的prepare。優先順序為:dev->pm_domain->ops、dev->type->pm、dev->class->pm、dev->bus->pm、dev->driver->pm(這個優先順序同樣適用於其它callbacks)。
5)如果得到有限的prepare函式,呼叫並返回結果。
dpm_suspend,執行所有裝置的“->suspend() callback(s)”,其內部動作和dpm_prepare類似:
1)遍歷dpm_list,依次取出掛在該list中的device指標。
2)呼叫內部介面device_suspend,執行實際的prepare動作。該介面會返回執行的結果。
3)如果suspend失敗,將該裝置的資訊記錄在一個struct suspend_stats型別的陣列中,並列印錯誤錯誤資訊。
4)最後將裝置從其它連結串列(如dpm_prepared_list),轉移到dpm_suspended_list連結串列中。
內部介面device_suspend的動作和device_prepare類似,這裡不再描述了。
dpm_suspend_start,依次執行dpm_prepare和dpm_suspend兩個動作。
dpm_suspend_end,依次執行所有裝置的“->suspend_late() callback(s)”以及所有裝置的“->suspend_noirq() callback(s)”。動作和上面描述的類似,這裡不再說明了。
dpm_resume、dpm_complete、dpm_resume_start、dpm_resume_end,是電源管理過程的喚醒動作,和dpm_suspend_xxx系列的介面類似。不再說明了。