Linux電源管理(4)-Power Manager Interface【轉】


本文轉載自:http://www.wowotech.net/pm_subsystem/pm_interface.html

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系列的接口類似。不再說明了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM