本文轉載自:http://www.wowotech.net/pm_subsystem/pm_interface.htmlhtml
Linux電源管理中,至關多的部分是在處理Hibernate、Suspend、Runtime PM等功能。而這些功能都基於一套類似的邏輯,即「Power management interface」。該Interface的代碼實現於「include/linux/pm.h」、「drivers/base/power/main.c」等文件中。主要功能是:對下,定義Device PM相關的回調函數,讓各個Driver實現;對上,實現統一的PM操做函數,供PM核心邏輯調用。linux
所以在對Hibernate、Suspend、Runtime PM等功能解析以前,有必要先熟悉一下PM Interface,這就是本文的主要目的。數組
在一個系統中,數量最多的是設備,耗電最多的也是設備,所以設備的電源管理是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,就會不可避免的改動這些數據結構。dom
因而新版本的內核,就將這些Callbacks統一封裝爲一個數據結構----struct dev_pm_ops,上層的數據結構只須要包含這個結構便可。這樣若是須要增長或者修改PM callbacks,就不用改動上層結構了(這就是軟件設計中抽象和封裝的生動體現,像藝術同樣優雅)。固然,內核爲了兼容舊的設計,也保留了上述的suspend/resume類型的callbacks,只是已不建議使用,本文就再也不介紹它們了。函數
相信每個熟悉了舊版本內核的Linux工程師,看到struct dev_pm_ops時都會虎軀一震,這玩意也太複雜了吧!不信您請看:spa
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-.net
-------->resume_noirq—>resume_early—>resume-->complete」。hibernate
但因爲這些callbacks須要由具體的設備Driver實現,這就要求驅動工程師在設計每一個Driver時,清晰的知道這些callbacks的使用場景、是否須要實現、怎麼實現,這纔是struct dev_pm_ops的複雜之處。設計
Linux kernel對struct dev_pm_ops的註釋已經很是詳細了,但要弄清楚每一個callback的使用場景、背後的思考,並非一件容易的事情。所以蝸蝸不許備在本文對它們進行過多的解釋,而打算結合具體的電源管理行爲,基於具體的場景,再進行解釋。
咱們在介紹「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存在的,那麼怎麼處理這些設備的電源管理呢?就是經過設備的電源域實現的。
內核在定義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系列的接口相似。再也不說明了。