1. 前言
終于可以寫Runtime PM(后面簡稱RPM)了,說實話,蝸蝸有點小激動。因為從個人的角度講,我很推崇使用RPM進行日常的動態(tài)電源管理,而不是suspend機制。
軟件工程的基本思想就是模塊化:高內(nèi)聚和低耦合。通俗地講呢,就是“各人自掃門前雪”,盡量掃好自己的(高內(nèi)聚),盡量不和別人交互(低耦合)。而RPM正體現(xiàn)了這一思想:每個設備(包括CPU)都處理好自身的電源管理工作,盡量以最低的能耗完成交代的任務,盡量在不需要工作的時候進入低功耗狀態(tài),盡量不和其它模塊有過多耦合。每個設備都是最節(jié)省的話,整個系統(tǒng)一定是最節(jié)省的,最終達到無所謂睡、無所謂醒的天人合一狀態(tài)。
講到這里想到自己的一則趣事:大學時,蝸蝸是寢室長,但不愛打掃衛(wèi)生,于是就提出一個口號,“不污染,不治理;誰污染,誰治理”。結果呢,大家猜就是了,呵呵。言歸正傳,開始吧。
2. Runtime PM的軟件框架
聽多了RPM的傳說,有種莫名的恐懼,覺的會很復雜。但看代碼,也就是“drivers/base/power/runtime.c”中1400行而已。
從設計思路上講,它確實簡單。下面是一個大概的軟件框架:
device driver(或者driver所在的bus、class等)需要提供3個回調(diào)函數(shù),runtime_suspend、runtime_resume和runtime_idle,分別用于suspend device、resume device和idle device。它們一般由RPM core在合適的時機調(diào)用,以便降低device的power consumption。
而調(diào)用的時機,最終是由device driver決定的。driver會在適當?shù)牟僮鼽c,調(diào)用RPM core提供的put和get系列的helper function,匯報device的當前狀態(tài)。RPM core會為每個device維護一個引用計數(shù),get時增加計數(shù)值,put時減少計數(shù)值,當計數(shù)為0時,表明device不再被使用,可以立即或一段時間后suspend,以節(jié)省功耗。
好吧,說總是簡單,那做呢?很不幸,到目前為止,linux kernel的runtime PM還是很復雜。這里的復雜,不是從實現(xiàn)的角度,而是從對外的角度。在“include\linux\pm_runtime.h”中,RPM提供了將近50個接口。軟件模塊化的設計理念中,最重要的一個原則就是提供簡潔的接口。很顯然,RPM沒有做到!
無論RPM面對的問題有多么復雜,無論理由有多么充分,它也應堅守“簡潔性”這一原則。否則,結果只有一個----無人敢用。這就是當前Linux kernel電源管理中“Opportunistic suspend”和RPM兩種機制并存的原因。但是,就算現(xiàn)狀不理想,也不能否認RPM的先進性,在當前以及未來很長的一段時間內(nèi),它會是kernel電源管理更新比較活躍的部分,因為可以做的還很多。
鑒于這個現(xiàn)狀,本文以及后續(xù)RPM有關的文章,會選取最新的kernel(當前為linux-3.17),以便及時同步相關的更新。
3. Runtime PM的運行機制
3.1 核心機制
RPM的核心機制是這樣的:
1)為每個設備維護一個引用計數(shù)(device-》power.usage_count),用于指示該設備的使用狀態(tài)。
2)需要使用設備時,device driver調(diào)用pm_runtime_get(或pm_runtime_get_sync)接口,增加引用計數(shù);不再使用設備時,device driver調(diào)用pm_runtime_put(或pm_runtime_put_sync)接口,減少引用計數(shù)。
3)每一次put,RPM core都會判斷引用計數(shù)的值。如果為零,表示該設備不再使用(idle)了,則使用異步(ASYNC)或同步(SYNC)的方式,調(diào)用設備的.runtime_idle回調(diào)函數(shù)。
4).runtime_idle的存在,是為了在idle和suspend之間加一個緩沖,避免頻繁的suspend/resume操作。因此它的職責是:判斷設備是否具備suspend的條件,如果具備,在合適的時機,suspend設備。
可以不提供,RPM core會使用異步(ASYNC)或同步(SYNC)的方式,調(diào)用設備的.runtime_suspend回調(diào)函數(shù),suspend設備,同時記錄設備的PM狀態(tài);
可以調(diào)用RPM core提供helper函數(shù)(pm_runtime_autosuspend_expiration、pm_runtime_autosuspend、pm_request_autosuspend),要求在指定的時間后,suspend設備。
5)pm_runtime_autosuspend、pm_request_autosuspend等接口,會起一個timer,并在timer到期后,使用異步(ASYNC)或同步(SYNC)的方式,調(diào)用設備的.runtime_suspend回調(diào)函數(shù),suspend設備,同時記錄設備的PM狀態(tài)。
6)每一次get,RPM core都會判斷設備的PM狀態(tài),如果不是active,則會使用異步(ASYNC)或同步(SYNC)的方式,調(diào)用設備的.runtime_resume回調(diào)函數(shù),resume設備。
注1:Runtime PM中的“suspend”,不一定要求設備必須進入低功耗狀態(tài),而是要求設備在suspend后,不再處理數(shù)據(jù),不再和CPUs、RAM進行任何的交互,直到設備的.runtime_resume被調(diào)用。因為此時設備的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和它們在本質(zhì)上是一樣的,都是實時的向PM core報告“我不工作了,可以睡了”、“我要工作了,不能睡(或醒來吧)”。不同的是:wakeup events和RPM的報告者是內(nèi)核空間drivers,而wakeup lock是用戶空間進程;wakeup events和wakelock涉及的睡眠對象是整個系統(tǒng),包括CPU和所有的devices,而RPM是一個一個獨立的device(CPU除外,它由cpu idle模塊處理,可看作RPM的特例)。
3.2 get和put的時機
這個話題的本質(zhì)是:device idle的判斷標準是什么?
再回憶一下“autosleep”中有關“Opportunistic suspend”的討論,對“Opportunistic suspend”而言,suspend時機的判斷是相當困難的,因為整機的運行環(huán)境比較復雜。而每一個具體設備的idle,就容易多了,這就是Runtime PM的優(yōu)勢?;氐竭@個話題上,對device而言,什么是idle?
device是通過用戶程序為用戶提供服務的,而服務的方式分為兩種:接受指令,做事情(被動);上報事件(主動,一般通過中斷的方式)。因此,設備active的時間段,包括【接受指令,完成指令】和【事件到達,由driver記錄下來】兩個。除此之外的時間,包括driver從用戶程序獲得指令(以及相關的數(shù)據(jù))、driver將事件(以及相關的數(shù)據(jù))交給應用程序,都是idle時間。
那idle時間是否應立即suspend以節(jié)省功耗?不一定,要具體場景具體對待:例如網(wǎng)絡傳輸,如果網(wǎng)絡連接正常,那么在可預期的、很短的時間內(nèi),設備又會active(傳輸網(wǎng)絡數(shù)據(jù)),如果頻繁suspend,會降低性能,且不會省電;比如用戶按鍵,具有突發(fā)性,因而可以考慮suspend;等等。
由于get和put正是設備idle狀態(tài)的切換點,因此get和put的時機就容易把握了:
1)主動訪問設備時,如寫寄存器、發(fā)起數(shù)據(jù)傳輸?shù)鹊?,get,增加引用計數(shù),告訴RPM core設備active;訪問結束后,put,減小引用計數(shù),告訴RPM core設備可能idle。
2)設備有事件通知時,get(可能在中斷處理中);driver處理完事件后,put。
注3:以上只是理論場景,實際可以放寬,以減小設計的復雜度。
3.3 異步(ASYNC)和同步(SYNC)
設備驅(qū)動代碼可在進程和中斷兩種上下文執(zhí)行,因此put和get等接口,要么是由用戶進程調(diào)用,要么是由中斷處理函數(shù)調(diào)用。由于這些接口可能會執(zhí)行device的.runtime_xxx回調(diào)函數(shù),而這些接口的執(zhí)行時間是不確定的,有些可能還會睡眠等待。這對用戶進程或者中斷處理函數(shù)來說,是不能接受的。
因此,RPM core提供的默認接口(pm_runtime_get/pm_runtime_put等),采用異步調(diào)用的方式(由ASYNC flag表示),啟動一個work queue,在單獨的線程中,調(diào)用.runtime_xxx回調(diào)函數(shù),這可以保證設備驅(qū)動之外的其它模塊正常運行。
另外,如果設備驅(qū)動清楚地知道自己要做什么,也可以使用同步接口(pm_runtime_get_sync/pm_runtime_put_sync等),它們會直接調(diào)用.runtime_xxx回調(diào)函數(shù),不過,后果自負!
3.4 Runtime PM過程中的同步問題
由于.runtime_xxx回調(diào)函數(shù)可能采用異步的形式調(diào)用,以及Generic PM suspend和RPM并存的現(xiàn)狀,要求RPM要小心處理同步問題,包括:
多個.runtime_suspend請求之間的同步;
多個.runtime_resume請求之間的同步;
多個.runtime_idle請求之間的同步;
.runtime_suspend請求和.runtime_resume請求之間的同步;
.runtime_suspend請求和system suspend之間的同步;
.runtime_resume請求和system resume之間的同步;
等等。
由此可知,RPM core將會有相當一部分代碼,用來處理同步問題。
3.5 級聯(lián)設備之間的Runtime PM
struct device結構中,有一個parent指針,指向該設備的父設備(沒有的話為空)。父設備通常是Bus、host controller,設備的正常工作,依賴父設備。體現(xiàn)在RPM中,就是如下的行為:
1)parent設備下任何一個設備處于active狀態(tài),parent必須active。
2)parent設備下任何一個設備idle了,要通知parent,parent以此記錄處于active狀態(tài)的child設備個數(shù)。
3)parent設備下所有子設備都idle了,parent才可以idle。
以上行為RPM core會自動處理,不需要驅(qū)動工程師太過操心。
3.6 device的runtime status及其初始狀態(tài)
在Runtime Power Management的過程中,device可處于四種狀態(tài):RPM_ACTIVE、RPM_RESUMING、RPM_SUSPENDED和RPM_SUSPENDING。
RPM_ACTIVE,設備處于正常工作的狀態(tài),表示設備的.runtime_resume回調(diào)函數(shù)執(zhí)行成功;
RPM_SUSPENDED,設備處于suspend狀態(tài),表示設備.runtime_suspend回調(diào)函數(shù)執(zhí)行成功;
RPM_RESUMING,設備的.runtime_resume正在被執(zhí)行;
RPM_SUSPENDING,設備的.runtime_suspend正在被執(zhí)行。
注4:前面說過,.runtime_idle只是suspend前的過渡,因此runtime status和idle無關。
device注冊時,設備模型代碼會調(diào)用pm_runtime_init接口,將設備的runtime status初始化為RPM_SUSPENDED,而kernel并不知道某個設備初始化時的真正狀態(tài),因此設備驅(qū)動需要根據(jù)實際情況,調(diào)用RPM的helper函數(shù),將自身的status設置正確。
4. runtime PM的API匯整
RPM提供的API位于“include/linux/pm_runtime.h”中,在這里先瀏覽一下,目的有二:一是對前面描述的RPM運行機制有一個感性的認識;二是為后面分析RPM的運行機制做準備。
注5:我會把和驅(qū)動編寫相關度較高的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);
這三個函數(shù)是RPM的idle、put/suspend、get/resume等操作的基礎,根據(jù)rpmflag,有著不同的操作邏輯。后續(xù)很多API,都是基于它們?nèi)齻€。一般不會在設備驅(qū)動中直接使用。
extern int pm_schedule_suspend(struct device *dev, unsigned int delay);
在指定的時間后(delay,單位是ms),suspend設備。該接口為異步調(diào)用,不會更改設備的引用計數(shù),可在driver的.rpm_idle中調(diào)用,免去driver自己再啟一個timer的煩惱。
extern void pm_runtime_enable(struct device *dev);
extern void pm_runtime_disable(struct device *dev);
設備RPM功能的enable/disable,可嵌套調(diào)用,會使用一個變量(dev-》power.disable_depth)記錄disable的深度。只要disable_depth大于零,就意味著RPM功能不可使用,很多的API調(diào)用(如suspend/reesume/put/get等)會返回失敗。
RPM初始化時,會將所有設備的disable_depth置為1,也就是disable狀態(tài),driver初始化完畢后,要根據(jù)設備的時機狀態(tài),調(diào)用這兩個函數(shù),將RPM狀態(tài)設置正確。
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功能。這兩個函數(shù)用來控制是否開啟該功能(默認開啟)。
extern int pm_runtime_barrier(struct device *dev);
這名字起的?。?!
由3.3的描述可知,很多RPM請求都是異步的,這些請求會掛到一個名稱為“pm_wq”的工作隊列上,這個函數(shù)的目的,就是清空這個隊列,另外如果有resume請求,同步等待resume完成。好復雜,希望driver永遠不要用到它?。?/p>
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);
幾個通用的函數(shù),一般給subsystem的RPM driver使用,直接調(diào)用devie driver的相應的callback函數(shù)。
extern void pm_runtime_no_callbacks(struct device *dev);
告訴RPM core自己沒有回調(diào)函數(shù),不用再調(diào)用了(或者調(diào)用都是成功的),真啰嗦。
extern void pm_runtime_irq_safe(struct device *dev);
告訴RPM core,如下函數(shù)可以在中斷上下文調(diào)用:
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設備,如果條件許可,就會執(zhí)行相應的callback函數(shù)。driver盡量不要使用它們。
static inline int pm_request_idle(struct device *dev)
static inline int pm_request_resume(struct device *dev)
和上面類似,不過調(diào)用方式為異步。盡量不要使用它們。
static inline int pm_runtime_get(struct device *dev)
static inline int pm_runtime_put(struct device *dev)
增加/減少設備的使用計數(shù),并判斷是否為0,如果為零,嘗試調(diào)用設備的idle callback,如果不為零,嘗試調(diào)用設備的resume callback。
這兩個接口是RPM的正統(tǒng)接口啊,多多使用!
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)
和上面類似,只不過為同步調(diào)用。另外提供了一個可直接調(diào)用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所提供的功能有些重疊,重疊的有點啰嗦??赡茉O計者為了提供更多的便利,可過渡的便利和自由,反而是一種束縛和煩惱!
5. runtime PM的使用步驟
覺得上面已經(jīng)講了,就不再重復了。
評論
查看更多