1. 程式人生 > >Linux電源管理(4)_Power Management Interface

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