1. 程式人生 > >Linux電源管理(11)_Runtime PM之功能描述

Linux電源管理(11)_Runtime PM之功能描述

1. 前言

終於可以寫Runtime PM(後面簡稱RPM)了,說實話,蝸蝸有點小激動。因為從個人的角度講,我很推崇使用RPM進行日常的動態電源管理,而不是suspend機制。

軟體工程的基本思想就是模組化:高內聚和低耦合。通俗地講呢,就是“各人自掃門前雪”,儘量掃好自己的(高內聚),儘量不和別人互動(低耦合)。而RPM正體現了這一思想:每個裝置(包括CPU)都處理好自身的電源管理工作,儘量以最低的能耗完成交代的任務,儘量在不需要工作的時候進入低功耗狀態,儘量不和其它模組有過多耦合。每個裝置都是最節省的話,整個系統一定是最節省的,最終達到無所謂睡、無所謂醒的天人合一狀態。

講到這裡想到自己的一則趣事:大學時,蝸蝸是寢室長,但不愛打掃衛生,於是就提出一個口號,“不汙染,不治理;誰汙染,誰治理”。結果呢,大家猜就是了,呵呵。言歸正傳,開始吧。

2. Runtime PM的軟體框架

聽多了RPM的傳說,有種莫名的恐懼,覺的會很複雜。但看程式碼,也就是“drivers/base/power/runtime.c”中1400行而已。

從設計思路上講,它確實簡單。下面是一個大概的軟體框架:

Runtime PM architecture

device driver(或者driver所在的bus、class等)需要提供3個回撥函式,runtime_suspend、runtime_resume和runtime_idle,分別用於suspend device、resume device和idle device。它們一般由RPM core在合適的時機呼叫,以便降低device的power consumption。

而呼叫的時機,最終是由device driver決定的。driver會在適當的操作點,呼叫RPM core提供的put和get系列的helper function,彙報device的當前狀態。RPM core會為每個device維護一個引用計數,get時增加計數值,put時減少計數值,當計數為0時,表明device不再被使用,可以立即或一段時間後suspend,以節省功耗。

好吧,說總是簡單,那做呢?很不幸,到目前為止,linux kernel的runtime PM還是很複雜。這裡的複雜,不是從實現的角度,而是從對外的角度。在“include\linux\pm_runtime.h”中,RPM提供了將近50個介面。軟體模組化的設計理念中,最重要的一個原則就是提供簡潔的介面

。很顯然,RPM沒有做到!

無論RPM面對的問題有多麼複雜,無論理由有多麼充分,它也應堅守“簡潔性”這一原則。否則,結果只有一個----無人敢用。這就是當前Linux kernel電源管理中“Opportunistic suspend”和RPM兩種機制並存的原因。但是,就算現狀不理想,也不能否認RPM的先進性,在當前以及未來很長的一段時間內,它會是kernel電源管理更新比較活躍的部分,因為可以做的還很多。

鑑於這個現狀,本文以及後續RPM有關的文章,會選取最新的kernel(當前為linux-3.17),以便及時同步相關的更新。

3. Runtime PM的執行機制

3.1 核心機制

RPM的核心機制是這樣的:

1)為每個裝置維護一個引用計數(device->power.usage_count),用於指示該裝置的使用狀態。

2)需要使用裝置時,device driver呼叫pm_runtime_get(或pm_runtime_get_sync)介面,增加引用計數;不再使用裝置時,device driver呼叫pm_runtime_put(或pm_runtime_put_sync)介面,減少引用計數。

3)每一次put,RPM core都會判斷引用計數的值。如果為零,表示該裝置不再使用(idle)了,則使用非同步(ASYNC)或同步(SYNC)的方式,呼叫裝置的.runtime_idle回撥函式。

4).runtime_idle的存在,是為了在idle和suspend之間加一個緩衝,避免頻繁的suspend/resume操作。因此它的職責是:判斷裝置是否具備suspend的條件,如果具備,在合適的時機,suspend裝置。

可以不提供,RPM core會使用非同步(ASYNC)或同步(SYNC)的方式,呼叫裝置的.runtime_suspend回撥函式,suspend裝置,同時記錄裝置的PM狀態;

可以呼叫RPM core提供helper函式(pm_runtime_autosuspend_expiration、pm_runtime_autosuspend、pm_request_autosuspend),要求在指定的時間後,suspend裝置。

5)pm_runtime_autosuspend、pm_request_autosuspend等介面,會起一個timer,並在timer到期後,使用非同步(ASYNC)或同步(SYNC)的方式,呼叫裝置的.runtime_suspend回撥函式,suspend裝置,同時記錄裝置的PM狀態。

6)每一次get,RPM core都會判斷裝置的PM狀態,如果不是active,則會使用非同步(ASYNC)或同步(SYNC)的方式,呼叫裝置的.runtime_resume回撥函式,resume裝置。

注1:Runtime PM中的“suspend”,不一定要求裝置必須進入低功耗狀態,而是要求裝置在suspend後,不再處理資料,不再和CPUs、RAM進行任何的互動,直到裝置的.runtime_resume被呼叫。因為此時裝置的parent(如bus controller)、CPU是、RAM等,都有可能因為suspend而不再工作,如果裝置再有任何動作,都會造成不可預期的異常。下面是“Documentation\power\runtime_pm.txt”中的解釋,供大家參考:

* Once the subsystem-level suspend callback (or the driver suspend callback, 
  if invoked directly) has completed successfully for the given device, the PM 
  core regards the device as suspended, which need not mean that it has been 
  put into a low power state.  It is supposed to mean, however, that the 
  device will not process data and will not communicate with the CPU(s) and 
  RAM until the appropriate resume callback is executed for it.  The runtime 
  PM status of a device after successful execution of the suspend callback is 
  'suspended'.

注2:回憶一下wakeup eventswakeup lock,Runtime PM和它們在本質上是一樣的,都是實時的向PM core報告“我不工作了,可以睡了”、“我要工作了,不能睡(或醒來吧)”。不同的是:wakeup events和RPM的報告者是核心空間drivers,而wakeup lock是使用者空間程序;wakeup events和wakelock涉及的睡眠物件是整個系統,包括CPU和所有的devices,而RPM是一個一個獨立的device(CPU除外,它由cpu idle模組處理,可看作RPM的特例)。

3.2 get和put的時機

這個話題的本質是:device idle的判斷標準是什麼?

再回憶一下“autosleep”中有關“Opportunistic suspend”的討論,對“Opportunistic suspend”而言,suspend時機的判斷是相當困難的,因為整機的執行環境比較複雜。而每一個具體裝置的idle,就容易多了,這就是Runtime PM的優勢。回到這個話題上,對device而言,什麼是idle?

device是通過使用者程式為使用者提供服務的,而服務的方式分為兩種:接受指令,做事情(被動);上報事件(主動,一般通過中斷的方式)。因此,裝置active的時間段,包括【接受指令,完成指令】和【事件到達,由driver記錄下來】兩個。除此之外的時間,包括driver從使用者程式獲得指令(以及相關的資料)、driver將事件(以及相關的資料)交給應用程式,都是idle時間。

那idle時間是否應立即suspend以節省功耗?不一定,要具體場景具體對待:例如網路傳輸,如果網路連線正常,那麼在可預期的、很短的時間內,裝置又會active(傳輸網路資料),如果頻繁suspend,會降低效能,且不會省電;比如使用者按鍵,具有突發性,因而可以考慮suspend;等等。

由於get和put正是裝置idle狀態的切換點,因此get和put的時機就容易把握了:

1)主動訪問裝置時,如寫暫存器、發起資料傳輸等等,get,增加引用計數,告訴RPM core裝置active;訪問結束後,put,減小引用計數,告訴RPM core裝置可能idle。

2)裝置有事件通知時,get(可能在中斷處理中);driver處理完事件後,put。

注3:以上只是理論場景,實際可以放寬,以減小設計的複雜度。

3.3 非同步(ASYNC)和同步(SYNC)

裝置驅動程式碼可在程序和中斷兩種上下文執行,因此put和get等介面,要麼是由使用者程序呼叫,要麼是由中斷處理函式呼叫。由於這些介面可能會執行device的.runtime_xxx回撥函式,而這些介面的執行時間是不確定的,有些可能還會睡眠等待。這對使用者程序或者中斷處理函式來說,是不能接受的。

因此,RPM core提供的預設介面(pm_runtime_get/pm_runtime_put等),採用非同步呼叫的方式(由ASYNC flag表示),啟動一個work queue,在單獨的執行緒中,呼叫.runtime_xxx回撥函式,這可以保證裝置驅動之外的其它模組正常執行。

另外,如果裝置驅動清楚地知道自己要做什麼,也可以使用同步介面(pm_runtime_get_sync/pm_runtime_put_sync等),它們會直接呼叫.runtime_xxx回撥函式,不過,後果自負!

3.4 Runtime PM過程中的同步問題

由於.runtime_xxx回撥函式可能採用非同步的形式呼叫,以及Generic PM suspend和RPM並存的現狀,要求RPM要小心處理同步問題,包括:

多個.runtime_suspend請求之間的同步; 
多個.runtime_resume請求之間的同步; 
多個.runtime_idle請求之間的同步; 
.runtime_suspend請求和.runtime_resume請求之間的同步; 
.runtime_suspend請求和system suspend之間的同步; 
.runtime_resume請求和system resume之間的同步; 
等等。

由此可知,RPM core將會有相當一部分程式碼,用來處理同步問題。

3.5 級聯裝置之間的Runtime PM

struct device結構中,有一個parent指標,指向該裝置的父裝置(沒有的話為空)。父裝置通常是Bus、host controller,裝置的正常工作,依賴父裝置。體現在RPM中,就是如下的行為:

1)parent裝置下任何一個裝置處於active狀態,parent必須active。

2)parent裝置下任何一個裝置idle了,要通知parent,parent以此記錄處於active狀態的child裝置個數。

3)parent裝置下所有子裝置都idle了,parent才可以idle。

以上行為RPM core會自動處理,不需要驅動工程師太過操心。

3.6 device的runtime status及其初始狀態

在Runtime Power Management的過程中,device可處於四種狀態:RPM_ACTIVE、RPM_RESUMING、RPM_SUSPENDED和RPM_SUSPENDING。

RPM_ACTIVE,裝置處於正常工作的狀態,表示裝置的.runtime_resume回撥函式執行成功;

RPM_SUSPENDED,裝置處於suspend狀態,表示裝置.runtime_suspend回撥函式執行成功;

RPM_RESUMING,裝置的.runtime_resume正在被執行;

RPM_SUSPENDING,裝置的.runtime_suspend正在被執行。

注4:前面說過,.runtime_idle只是suspend前的過渡,因此runtime status和idle無關。

device註冊時,裝置模型程式碼會呼叫pm_runtime_init介面,將裝置的runtime status初始化為RPM_SUSPENDED,而kernel並不知道某個裝置初始化時的真正狀態,因此裝置驅動需要根據實際情況,呼叫RPM的helper函式,將自身的status設定正確。

4. runtime PM的API彙整

RPM提供的API位於“include/linux/pm_runtime.h”中,在這裡先瀏覽一下,目的有二:一是對前面描述的RPM執行機制有一個感性的認識;二是為後面分析RPM的執行機制做準備。

注5:我會把和驅動編寫相關度較高的API加粗,其它的能不用就不用、能不看就不看!

extern int __pm_runtime_idle(struct device *dev, int rpmflags); 
extern int __pm_runtime_suspend(struct device *dev, int rpmflags); 
extern int __pm_runtime_resume(struct device *dev, int rpmflags);

這三個函式是RPM的idle、put/suspend、get/resume等操作的基礎,根據rpmflag,有著不同的操作邏輯。後續很多API,都是基於它們三個。一般不會在裝置驅動中直接使用。

extern int pm_schedule_suspend(struct device *dev, unsigned int delay);

在指定的時間後(delay,單位是ms),suspend裝置。該介面為非同步呼叫,不會更改裝置的引用計數,可在driver的.rpm_idle中呼叫,免去driver自己再啟一個timer的煩惱。

extern void pm_runtime_enable(struct device *dev); 
extern void pm_runtime_disable(struct device *dev);

裝置RPM功能的enable/disable,可巢狀呼叫,會使用一個變數(dev->power.disable_depth)記錄disable的深度。只要disable_depth大於零,就意味著RPM功能不可使用,很多的API呼叫(如suspend/reesume/put/get等)會返回失敗。

RPM初始化時,會將所有裝置的disable_depth置為1,也就是disable狀態,driver初始化完畢後,要根據裝置的時機狀態,呼叫這兩個函式,將RPM狀態設定正確。

extern void pm_runtime_allow(struct device *dev); 
extern void pm_runtime_forbid(struct device *dev);

RPM core通過sysfs(drivers/base/power/sysfs.c),為每個裝置提供一個“/sys/devices/.../power/control”檔案,通過該檔案可讓使用者空間程式直接訪問device的RPM功能。這兩個函式用來控制是否開啟該功能(預設開啟)。

extern int pm_runtime_barrier(struct device *dev);

這名字起的!!!

由3.3的描述可知,很多RPM請求都是非同步的,這些請求會掛到一個名稱為“pm_wq”的工作佇列上,這個函式的目的,就是清空這個佇列,另外如果有resume請求,同步等待resume完成。好複雜,希望driver永遠不要用到它!!

extern int pm_generic_runtime_idle(struct device *dev); 
extern int pm_generic_runtime_suspend(struct device *dev); 
extern int pm_generic_runtime_resume(struct device *dev);

幾個通用的函式,一般給subsystem的RPM driver使用,直接呼叫devie driver的相應的callback函式。

extern void pm_runtime_no_callbacks(struct device *dev);

告訴RPM core自己沒有回撥函式,不用再呼叫了(或者呼叫都是成功的),真囉嗦。

extern void pm_runtime_irq_safe(struct device *dev);

告訴RPM core,如下函式可以在中斷上下文呼叫: 
pm_runtime_idle() 
pm_runtime_suspend() 
pm_runtime_autosuspend() 
pm_runtime_resume() 
pm_runtime_get_sync() 
pm_runtime_put_sync() 
pm_runtime_put_sync_suspend() 
pm_runtime_put_sync_autosuspend()

static inline int pm_runtime_idle(struct device *dev) 
static inline int pm_runtime_suspend(struct device *dev) 
static inline int pm_runtime_resume(struct device *dev)

直接使用同步的方式,嘗試idle/suspend/resume裝置,如果條件許可,就會執行相應的callback函式。driver儘量不要使用它們。

static inline int pm_request_idle(struct device *dev) 
static inline int pm_request_resume(struct device *dev)

和上面類似,不過呼叫方式為非同步。儘量不要使用它們。

static inline int pm_runtime_get(struct device *dev) 
static inline int pm_runtime_put(struct device *dev)

增加/減少裝置的使用計數,並判斷是否為0,如果為零,嘗試呼叫裝置的idle callback,如果不為零,嘗試呼叫裝置的resume callback。

這兩個介面是RPM的正統介面啊,多多使用!

static inline int pm_runtime_get_sync(struct device *dev) 
static inline int pm_runtime_put_sync(struct device *dev) 
static inline int pm_runtime_put_sync_suspend(struct device *dev)

和上面類似,只不過為同步呼叫。另外提供了一個可直接呼叫suspend的put介面,何必的!


static inline int pm_runtime_autosuspend(struct device *dev) 
static inline int pm_request_autosuspend(struct device *dev) 
static inline int pm_runtime_put_autosuspend(struct device *dev) 
static inline int pm_runtime_put_sync_autosuspend(struct device *dev)

autosuspend相關介面。所謂的autosuspend,就是在suspend的基礎上,增加一個timer,還是覺得有點囉嗦。不說了。

static inline void pm_runtime_use_autosuspend(struct device *dev) 
static inline void pm_runtime_dont_use_autosuspend(struct device *dev) 
extern void pm_runtime_set_autosuspend_delay(struct device *dev, int delay); 
extern unsigned long pm_runtime_autosuspend_expiration(struct device *dev);

控制是否使用autosuspend功能,以及設定/獲取autosuspend的超時值。

總結一下:總覺得這些API所提供的功能有些重疊,重疊的有點囉嗦。可能設計者為了提供更多的便利,可過渡的便利和自由,反而是一種束縛和煩惱!

5. runtime PM的使用步驟

覺得上面已經講了,就不再重複了。