1. 前言
Linux電源管理中,相當(dāng)多的部分是在處理Hibernate、Suspend、Runtime PM等功能。而這些功能都基于一套相似的邏輯,即“Power management interface”。該Interface的代碼實現(xiàn)于“include/linux/pm.h”、“drivers/base/power/main.c”等文件中。主要功能是:對下,定義Device PM相關(guān)的回調(diào)函數(shù),讓各個Driver實現(xiàn);對上,實現(xiàn)統(tǒng)一的PM操作函數(shù),供PM核心邏輯調(diào)用。
因此在對Hibernate、Suspend、Runtime PM等功能解析之前,有必要先熟悉一下PM Interface,這就是本文的主要目的。
2. device PM callbacks
在一個系統(tǒng)中,數(shù)量最多的是設(shè)備,耗電最多的也是設(shè)備,因此設(shè)備的電源管理是Linux電源管理的核心內(nèi)容。而設(shè)備電源管理最核心的操作就是:在合適的時機(如不再使用,如暫停使用),將設(shè)備置為合理的狀態(tài)(如關(guān)閉,如睡眠)。這就是device PM callbacks的目的:定義一套統(tǒng)一的方式,讓設(shè)備在特定的時機,步調(diào)一致的進入類似的狀態(tài)(可以想象一下軍訓(xùn)時的“一二一”口令)。
在舊版本的內(nèi)核中,這些PM callbacks分布在設(shè)備模型的大型數(shù)據(jù)結(jié)構(gòu)中,如struct bus_type中的suspend、suspend_late、resume、resume_late,如struct device_driver/struct class/struct device_type中的suspend、resume。很顯然這樣不具備良好的封裝特性,因為隨著設(shè)備復(fù)雜度的增加,簡單的suspend、resume已經(jīng)不能滿足電源管理的需求,就需要擴充PM callbacks,就會不可避免的改動這些數(shù)據(jù)結(jié)構(gòu)。
于是新版本的內(nèi)核,就將這些Callbacks統(tǒng)一封裝為一個數(shù)據(jù)結(jié)構(gòu)----struct dev_pm_ops,上層的數(shù)據(jù)結(jié)構(gòu)只需要包含這個結(jié)構(gòu)即可。這樣如果需要增加或者修改PM callbacks,就不用改動上層結(jié)構(gòu)了(這就是軟件設(shè)計中抽象和封裝的生動體現(xiàn),像藝術(shù)一樣優(yōu)雅)。當(dāng)然,內(nèi)核為了兼容舊的設(shè)計,也保留了上述的suspend/resume類型的callbacks,只是已不建議使用,本文就不再介紹它們了。
相信每一個熟悉了舊版本內(nèi)核的Linux工程師,看到struct dev_pm_ops時都會虎軀一震,這玩意也太復(fù)雜了吧!不信您請看:
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并不復(fù)雜,因為PM Core要做的就是在特定的電源管理階段,調(diào)用相應(yīng)的callbacks,例如在suspend/resume的過程中,PM Core會依次調(diào)用“prepare—>suspend—>suspend_late—>suspend_noirq-------wakeup--------->resume_noirq—>resume_early—>resume-->complete”。
但由于這些callbacks需要由具體的設(shè)備Driver實現(xiàn),這就要求驅(qū)動工程師在設(shè)計每個Driver時,清晰的知道這些callbacks的使用場景、是否需要實現(xiàn)、怎么實現(xiàn),這才是struct dev_pm_ops的復(fù)雜之處。
Linux kernel對struct dev_pm_ops的注釋已經(jīng)非常詳細了,但要弄清楚每個callback的使用場景、背后的思考,并不是一件容易的事情。因此蝸蝸不準(zhǔn)備在本文對它們進行過多的解釋,而打算結(jié)合具體的電源管理行為,基于具體的場景,再進行解釋。
3. device PM callbacks在設(shè)備模型中的體現(xiàn)
我們在介紹“Linux設(shè)備模型”時,曾多次提及電源管理相關(guān)的內(nèi)容,那時蝸蝸采取忽略的方式,暫不說明。現(xiàn)在是時候回過頭再去看看了。
Linux設(shè)備模型中的很多數(shù)據(jù)結(jié)構(gòu),都會包含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等結(jié)構(gòu)中的pm指針,比較容易理解,和舊的suspend/resume callbacks類似。我們重點關(guān)注一下device結(jié)構(gòu)中的power和pm_domain變量。
◆power變量
power是一個struct dev_pm_info類型的變量,也在“include/linux/pm.h”中定義。從蝸蝸一直工作于的Linux-2.6.23內(nèi)核,到寫這篇文章所用的Linux-3.10.29內(nèi)核,這個數(shù)據(jù)結(jié)構(gòu)可是一路發(fā)展壯大,從那時的只有4個字段,到現(xiàn)在有40多個字段,簡直是想起來什么就放什么??!
power變量主要保存PM相關(guān)的狀態(tài),如當(dāng)前的power_state、是否可以被喚醒、是否已經(jīng)prepare完成、是否已經(jīng)suspend完成等等。由于涉及的內(nèi)容非常多,我們在具體使用的時候,順便說明。
◆pm_domain指針
在當(dāng)前的內(nèi)核中,struct dev_pm_domain結(jié)構(gòu)只包含了一個struct dev_pm_ops ops。蝸蝸猜測這是從可擴展性方面考慮的,后續(xù)隨著內(nèi)核的進化,可能會在該結(jié)構(gòu)中添加其他內(nèi)容。
所謂的PM Domain(電源域),是針對“device”來說的。bus_type、device_driver、class、device_type等結(jié)構(gòu),本質(zhì)上代表的是設(shè)備驅(qū)動,電源管理的操作,由設(shè)備驅(qū)動負責(zé),是理所應(yīng)當(dāng)?shù)摹5趦?nèi)核中,由于各種原因,是允許沒有driver的device存在的,那么怎么處理這些設(shè)備的電源管理呢?就是通過設(shè)備的電源域?qū)崿F(xiàn)的。
4. device PM callbacks的操作函數(shù)
內(nèi)核在定義device PM callbacks數(shù)據(jù)結(jié)構(gòu)的同時,為了方便使用該數(shù)據(jù)結(jié)構(gòu),也定義了大量的操作API,這些API分為兩類。
◆通用的輔助性質(zhì)的API,直接調(diào)用指定設(shè)備所綁定的driver的、pm指針的、相應(yīng)的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接口是否存在,如果存在,直接調(diào)用并返回結(jié)果。
◆和整體電源管理行為相關(guān)的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,執(zhí)行所有設(shè)備的“->prepare() callback(s)”,內(nèi)部動作為:
1)遍歷dpm_list,依次取出掛在該list中的device指針。?
?? 【注1:設(shè)備模型在添加設(shè)備(device_add)時,會調(diào)用device_pm_add接口,將該設(shè)備添加到全局鏈表dpm_list中,以方便后續(xù)的遍歷操作?!?/p>
2)調(diào)用內(nèi)部接口device_prepare,執(zhí)行實際的prepare動作。該接口會返回執(zhí)行的結(jié)果。
3)如果執(zhí)行失敗,打印錯誤信息。
4)如果執(zhí)行成功,將dev->power.is_prepared(就是上面我們提到的struct dev_pm_info類型的變量)設(shè)為TRUE,表示設(shè)備已經(jīng)prepared了。同時,將該設(shè)備添加到dpm_prepared_list中(該鏈表保存了所有已經(jīng)處于prepared狀態(tài)的設(shè)備)。
內(nèi)部接口device_prepare的執(zhí)行動作為:
1)根據(jù)dev->power.syscore,斷該設(shè)備是否為syscore設(shè)備。如果是,則直接返回(因為syscore設(shè)備會單獨處理)。
2)在prepare時期,調(diào)用pm_runtime_get_noresume接口,關(guān)閉Runtime suspend功能。以避免由Runtime suspend造成的不能正常喚醒的Issue。該功能會在complete時被重新開啟。?
?? 【注2:pm_runtime_get_noresume的實現(xiàn)很簡單,就是增加該設(shè)備power變量的引用計數(shù)(dev->power.usage_count),Runtime PM會根據(jù)該計數(shù)是否大于零,判斷是否開啟Runtime PM功能。】
3)調(diào)用device_may_wakeup接口,根據(jù)當(dāng)前設(shè)備是否有wakeup source(dev->power.wakeup)以及是否允許wakeup(dev->power.can_wakeup),判定該設(shè)備是否是一個wakeup path(記錄在dev->power.wakeup_path中)。?
??? 【注3:設(shè)備的wake up功能,是指系統(tǒng)在低功耗狀態(tài)下(如suspend、hibernate),某些設(shè)備具備喚醒系統(tǒng)的功能。這是電源管理過程的一部分?!?/p>
4)根據(jù)優(yōu)先順序,獲得用于prepare的callback函數(shù)。由于設(shè)備模型有bus、driver、device等多個層級,而prepare接口可能由任意一個層級實現(xiàn)。這里的優(yōu)先順序是指,只要優(yōu)先級高的層級注冊了prepare,就會優(yōu)先使用它,而不會使用優(yōu)先級低的prepare。優(yōu)先順序為:dev->pm_domain->ops、dev->type->pm、dev->class->pm、dev->bus->pm、dev->driver->pm(這個優(yōu)先順序同樣適用于其它callbacks)。?
5)如果得到有限的prepare函數(shù),調(diào)用并返回結(jié)果。?
dpm_suspend,執(zhí)行所有設(shè)備的“->suspend() callback(s)”,其內(nèi)部動作和dpm_prepare類似:
1)遍歷dpm_list,依次取出掛在該list中的device指針。?
2)調(diào)用內(nèi)部接口device_suspend,執(zhí)行實際的prepare動作。該接口會返回執(zhí)行的結(jié)果。
3)如果suspend失敗,將該設(shè)備的信息記錄在一個struct suspend_stats類型的數(shù)組中,并打印錯誤錯誤信息。
4)最后將設(shè)備從其它鏈表(如dpm_prepared_list),轉(zhuǎn)移到dpm_suspended_list鏈表中。
內(nèi)部接口device_suspend的動作和device_prepare類似,這里不再描述了。
dpm_suspend_start,依次執(zhí)行dpm_prepare和dpm_suspend兩個動作。
dpm_suspend_end,依次執(zhí)行所有設(shè)備的“->suspend_late() callback(s)”以及所有設(shè)備的“->suspend_noirq() callback(s)”。動作和上面描述的類似,這里不再說明了。
dpm_resume、dpm_complete、dpm_resume_start、dpm_resume_end,是電源管理過程的喚醒動作,和dpm_suspend_xxx系列的接口類似。不再說明了。
?
評論
查看更多