終於能夠寫Runtime PM(後面簡稱RPM)了,說實話,蝸蝸有點小激動。由於從我的的角度講,我很推崇使用RPM進行平常的動態電源管理,而不是suspend機制。linux
軟件工程的基本思想就是模塊化:高內聚和低耦合。通俗地講呢,就是「各人自掃門前雪」,儘可能掃好本身的(高內聚),儘可能不和別人交互(低耦合)。而RPM正體現了這一思想:每一個設備(包括CPU)都處理好自身的電源管理工做,儘可能以最低的能耗完成交代的任務,儘可能在不須要工做的時候進入低功耗狀態,儘可能不和其它模塊有過多耦合。每一個設備都是最節省的話,整個系統必定是最節省的,最終達到無所謂睡、無所謂醒的天人合一狀態。api
講到這裏想到本身的一則趣事:大學時,蝸蝸是寢室長,但不愛打掃衛生,因而就提出一個口號,「不污染,不治理;誰污染,誰治理」。結果呢,你們猜就是了,呵呵。言歸正傳,開始吧。網絡
聽多了RPM的傳說,有種莫名的恐懼,覺的會很複雜。但看代碼,也就是「drivers/base/power/runtime.c」中1400行而已。app
從設計思路上講,它確實簡單。下面是一個大概的軟件框架:框架
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,以節省功耗。async
好吧,說老是簡單,那作呢?很不幸,到目前爲止,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),以便及時同步相關的更新。性能
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 events和wakeup lock,Runtime PM和它們在本質上是同樣的,都是實時的向PM core報告「我不工做了,能夠睡了」、「我要工做了,不能睡(或醒來吧)」。不一樣的是:wakeup events和RPM的報告者是內核空間drivers,而wakeup lock是用戶空間進程;wakeup events和wakelock涉及的睡眠對象是整個系統,包括CPU和全部的devices,而RPM是一個一個獨立的device(CPU除外,它由cpu idle模塊處理,可看做RPM的特例)。
這個話題的本質是: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:以上只是理論場景,實際能夠放寬,以減少設計的複雜度。
設備驅動代碼可在進程和中斷兩種上下文執行,所以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回調函數,不過,後果自負!
因爲.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之間的同步;
等等。
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會自動處理,不須要驅動工程師太過操心。
在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設置正確。
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所提供的功能有些重疊,重疊的有點囉嗦。可能設計者爲了提供更多的便利,可過渡的便利和自由,反而是一種束縛和煩惱!
以爲上面已經講了,就再也不重複了。