首先,回想一下wowo電源管理系列文章中提到的幾個PM特性:
B. Wake Lock
C. Auto Sleep
這篇文章就簡單簡單整理一下以上特性的在Driver中的使用場景,理解可能有偏差,大家多指教。來看看這個幾個特性的實現分別在內核代碼樹的位置:
WakeUp Count/WakeUp Source:
/linux/driver/base/power/wakeup.c
Wake Lock :
對用戶層的:/linux/kernel/power/wakelock.c
對內核層的:/linux/include/linux/wakelock.h
Auto Sleep:
/linux/kernel/power/autosleep.c
Runtime Suspend:
/linux/driver/base/power/runtime.c
有關PM的,集中在/kernel/power/目錄(PM Core),以及/driver/base/power/目錄(PM Function)。一個個來看看在驅動中怎麼用這些特性吧。
先來看看WakeUp Count/WakeUp Souce。
剛開始接觸這個東西,總容易被Wake Souce這個關鍵詞迷惑,第一反應是把這東西跟HW Wake Souce牽連起來,但其實這東西跟硬件沒什麼關係,只是一個軟件抽象出來的一種設備屬性,所以如果在驅動開發過程中,需要該設備具備將系統從Low Power Mode喚醒的功能,還是不要被這個特性的名字所迷惑爲好。
那麼,這個特性怎麼使用呢?回想一下wowo描述這個特性時提到的,wakeup count的目的是爲了解決同步問題,也即是爲了解決”在睡眠的過程中,接收到事件而想退出睡眠“的問題以及判斷“在用戶層發起睡眠請求時,當前系統是否符合睡眠條件”,關於wakeup_count在用戶層的應用可以簡單總結爲如下代碼:
- #define WAKEUP_COUNT "/sys/power/wakeup_count"
- #define POWER_STATE "/sys/power/state"
- int ret;
- int wakeup;
- FILE *f_wakeup = fopen(WAKE_COUNT, "rb+");
- FILE *f_state = fopne(POWER_STATE, "rb+");
- do {
- fscanf(f_wakeup, "%d", &wakeup);
- ret = fprintf(f_wakeup, "%d", wakeup);
- }while (ret < 0);
- fprintf(f_state, "mem");
用戶層的應用就不過多考慮了,這裏主要看Driver層。那麼,看看Driver中關於wc/ws的應用場景:1. 在事件處理完之前主動阻止suspend,有點類似於鎖,關於這點的應用就是之後要提到的對內核層的wake lock;2. 系統已經suspend了,喚醒系統上報事件,並在時間處理完之前主動阻止suspend,這就跟硬件喚醒源有關係了,而且肯定跟中斷有關係。
來看第一種應用:
- #include <xxx.h>
- ...
- #include <linux/platform_device.h>
- #include <linux/pm_wakeup.h>
- int xxx(struct device *dev)
- {
- pm_stay_awake(dev);
- ....
- pm_relax(dev);
- ...
- }
- int xxx_probe(struct platform_device *pdev)
- {
- struct device *dev = pdev->dev;
- ...
- device_init_wakeup(dev, true);
- ...
- }
- int __init xxx_init(void)
- {
- return platform_driver_register(&xxx_device_driver);
- }
- module_initcall(xxx_init);
- MODULE_LICENSE("GPL");
device_init_wakeup()給這個device賦予了ws的屬性,並且在執行xxx()函數過程可以阻止系統休眠。這種是主觀上的阻止,也即驅動開發者預見到這段代碼執行過程中不能休眠,從而主動給PM Core報告事件,這種使用場景跟中斷沒有關係,可以根據需求在任何內核執行路徑上報告事件,目的只是爲了阻止休眠而已,需要注意的是,這種設置是沒辦法喚醒已經休眠的系統的。
接下來看一種比較迷惑的情況:
- #include <xxx.h>
- ...
- #include <linux/platform_device.h>
- #include <linux/pm_wakeup.h>
- struct device * dev;
- int xxx_isr(int irq, void *dev_id)
- {
- pm_stay_awake(dev);
- ....
- pm_relax(dev);
- return IRQ_HANDLED;
- }
- int xxx_probe(struct platform_device *pdev)
- {
- int ret;
- dev = pdev->dev;
- ...
- ret = request_irq(irq, xxx_isr, xxx, xxx, xxx);
- ...
- device_init_wakeup(dev, true);
- ...
- }
- int __init xxx_init(void)
- {
- return platform_driver_register(&xxx_device_driver);
- }
- module_initcall(xxx_init);
- MODULE_LICENSE("GPL");
這種情況和IRQ有關,之前以爲這麼做了之後這個設備就具備硬件喚醒功能了,現在想想還真是....其實,這樣做了也只能保證在ISR執行期間不會休眠而已,這個設備中斷是否具備硬件喚醒功能和wc/ws還是沒什麼關係。
那麼,當確實需要具備硬件喚醒功能,怎麼辦呢?這裏就要談及一個硬件概念,喚醒中斷源。
這個硬件功能不同廠商的處理方法不一,如三星有些SoC從硬件電氣性上就使得某些中斷Pin具備了喚醒CPU的功能,再如CSR的SoC規定了只有若干個Pin可以作爲喚醒Pin使用,而Atmel的SoC則採用了軟件的方式來創造喚醒中斷源,也即先將所有中斷禁止,之後在使能開發者設置的喚醒中斷。說這麼多中斷的事,是因爲Suspend的最終形態是WFI,wait for interrupt。要具備硬件喚醒功能,好歹得是一個能到達CPU Core的中斷纔行,而這中間經過各級中斷管理器,並不說只要是一箇中斷就能喚醒系統的。
那麼,想要具備喚醒系統的功能,就要從中斷上下功夫,如下:
- #include <xxx.h>
- ...
- #include <linux/platform_device.h>
- #include <linux/pm_wakeup.h>
- struct device * dev;
- int xxx_isr(int irq, void *dev_id)
- {
- pm_stay_awake(dev);
- ....
- pm_relax(dev);
- return IRQ_HANDLED;
- }
- int xxx_probe(struct platform_device *pdev)
- {
- int ret;
- int irq;
- int flag;
- dev = pdev->dev;
- ...
- ret = request_irq(irq, xxx_isr, flag | IRQF_NO_SUSPEND, xxx, xxx);
- ...
- enable_irq_wake(irq);
- device_init_wakeup(dev, true);
- ...
- }
- int __init xxx_init(void)
- {
- return platform_driver_register(&xxx_device_driver);
- }
- module_initcall(xxx_init);
- MODULE_LICENSE("GPL");
這段代碼中對中斷做了兩個特殊的處理,一個是在申請中斷時加上了IRQF_NO_SUSPEND, 另一個是irq_enable_wake(irq); 這兩個函數都可以賦予IRQ喚醒系統的能力,前者是在suspend過程中dpm_suspend_noirq()->suspend_devices_irq()時保留IRQF_NO_SUSPEND類型的中斷響應,而後者直接跟irq_chip打交道,把喚醒功能的設置交由irq_chip driver處理。在Atmel的BSP中,這一個過程只是對一個名爲wakeups的變量做了位標記,以便其隨後在plat_form_ops->enter()時重新使能該中斷。從使用角度我覺得,irq_enable_wake()會是一個更爲保險且靈活的方法,畢竟更爲直接而且禁用喚醒功能方便,disable_irq_wake()即可。
關於Wake Lock的使用,可以參考關於wc/ws的使用場景的前兩種情況, wake_lock()只是對wc/ws應用的一種封裝。
可以把wake_lock()當做一種鎖來用,其內部使用了__pm_stay_awake()來使系統保持喚醒狀態。別忘記,這還是一種開發者主觀上的使用,即開發者希望wake_lock() ---> wake_unlock()期間系統不休眠,類似一種特殊的臨界區。
同時,相對wc/ws,wake_lock()更像是一種快捷方式,開發者不用去使能設備的喚醒屬性,也不用再去主動上報事件,只要在希望保持喚醒的特殊臨界區的前後使用wake lock就可以達到目的。
Auto Sleep跟Wake Lock是一對冤家,一個沒事就讓系統休眠,一個偏不讓系統休眠。簡單來講,Auto Sleep就是一直嘗試sleep,如通過條件不滿足,比如有事件pending(這可能是用戶層持有wake_lock, driver持有wake lock,以及上報的pending時間還沒有處理完),就返回,過一會再來嘗試。所以,如果使能了Auto Sleep這個特性,那寫驅動的時候就要考慮到某段代碼是否允許休眠後起來接着運行,如果不能,就要使用wake_lock()保護起來。畢竟Auto Sleep這個特性是對於Driver來說,是被動的,異步的,不可預期的,如果Driver不想讓PM Core逮着機會就休眠,就乾脆別讓系統休眠,而是先把自己的事情處理完了再說。
Runtime Suspend相對Auto Sleep而言就是更爲Driver主觀的行爲了,雖然不一定但可以預期,到在調用pm_runtime_put()之後該設備可能進入runtime_idle的狀態。runtime_idle的行爲是Driver確定的,之後的runtime_suspend也是Driver確定的,PM Core只是在維持設備的引用計數,當確定設備空閒時調用Driver提供的接口,使得設備進入idle或suspend。
所以,開發者需要注意的事情,是保證設備的電源行爲符合內核文檔所描述的行爲,即suspend的狀態下,不佔用CPU,不與主存交互等(但不一定需要進入low power mode),以及,使得設備的suspend/resume功能正常。另外就是同步問題,pm_runtime_put()之後的代碼可能會在runtime_idle之後執行,所以重要的代碼等還是在pm_runtime_put()之前完成更好。
拋磚引玉,大家多討論。