Linux電源管理(13)_Driver的電源管理

首先,回想一下wowo電源管理系列文章中提到的幾個PM特性:

A. WakeUP Count/WakeUp Source

B. Wake Lock

C. Auto Sleep

D. Runtime Suspend


這篇文章就簡單簡單整理一下以上特性的在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在用戶層的應用可以簡單總結爲如下代碼:

  1. #define WAKEUP_COUNT "/sys/power/wakeup_count"
  2. #define POWER_STATE "/sys/power/state"
  3.  
  4. int ret;
  5. int wakeup;
  6. FILE *f_wakeup = fopen(WAKE_COUNT, "rb+");
  7. FILE *f_state = fopne(POWER_STATE, "rb+");
  8.  
  9. do {
  10. fscanf(f_wakeup, "%d", &wakeup);
  11. ret = fprintf(f_wakeup, "%d", wakeup);
  12. }while (ret < 0);
  13.  
  14. fprintf(f_state, "mem");

用戶層的應用就不過多考慮了,這裏主要看Driver層。那麼,看看Driver中關於wc/ws的應用場景:1. 在事件處理完之前主動阻止suspend,有點類似於鎖,關於這點的應用就是之後要提到的對內核層的wake lock;2. 系統已經suspend了,喚醒系統上報事件,並在時間處理完之前主動阻止suspend,這就跟硬件喚醒源有關係了,而且肯定跟中斷有關係。

來看第一種應用:

  1. #include <xxx.h>
  2. ...
  3. #include <linux/platform_device.h>
  4. #include <linux/pm_wakeup.h>
  5.  
  6.  
  7. int xxx(struct device *dev)
  8. {
  9. pm_stay_awake(dev);
  10. ....
  11. pm_relax(dev);
  12. ...
  13. }
  14.  
  15. int xxx_probe(struct platform_device *pdev)
  16. {
  17. struct device *dev = pdev->dev;
  18. ...
  19.  
  20. device_init_wakeup(dev, true);
  21. ...
  22. }
  23.  
  24. int __init xxx_init(void)
  25. {
  26. return platform_driver_register(&xxx_device_driver);
  27. }
  28.  
  29. module_initcall(xxx_init);
  30. MODULE_LICENSE("GPL");
  31.  

device_init_wakeup()給這個device賦予了ws的屬性,並且在執行xxx()函數過程可以阻止系統休眠。這種是主觀上的阻止,也即驅動開發者預見到這段代碼執行過程中不能休眠,從而主動給PM Core報告事件,這種使用場景跟中斷沒有關係,可以根據需求在任何內核執行路徑上報告事件,目的只是爲了阻止休眠而已,需要注意的是,這種設置是沒辦法喚醒已經休眠的系統的。

接下來看一種比較迷惑的情況:

  1. #include <xxx.h>
  2. ...
  3. #include <linux/platform_device.h>
  4. #include <linux/pm_wakeup.h>
  5.  
  6. struct device * dev;
  7.  
  8. int xxx_isr(int irq, void *dev_id)
  9. {
  10. pm_stay_awake(dev);
  11. ....
  12. pm_relax(dev);
  13. return IRQ_HANDLED;
  14. }
  15.  
  16. int xxx_probe(struct platform_device *pdev)
  17. {
  18. int ret;
  19. dev = pdev->dev;
  20. ...
  21. ret = request_irq(irq, xxx_isr, xxx, xxx, xxx);
  22. ...
  23. device_init_wakeup(dev, true);
  24. ...
  25. }
  26.  
  27. int __init xxx_init(void)
  28. {
  29. return platform_driver_register(&xxx_device_driver);
  30. }
  31.  
  32. module_initcall(xxx_init);
  33. MODULE_LICENSE("GPL");



這種情況和IRQ有關,之前以爲這麼做了之後這個設備就具備硬件喚醒功能了,現在想想還真是....其實,這樣做了也只能保證在ISR執行期間不會休眠而已,這個設備中斷是否具備硬件喚醒功能和wc/ws還是沒什麼關係。

那麼,當確實需要具備硬件喚醒功能,怎麼辦呢?這裏就要談及一個硬件概念,喚醒中斷源。

這個硬件功能不同廠商的處理方法不一,如三星有些SoC從硬件電氣性上就使得某些中斷Pin具備了喚醒CPU的功能,再如CSR的SoC規定了只有若干個Pin可以作爲喚醒Pin使用,而Atmel的SoC則採用了軟件的方式來創造喚醒中斷源,也即先將所有中斷禁止,之後在使能開發者設置的喚醒中斷。說這麼多中斷的事,是因爲Suspend的最終形態是WFI,wait for interrupt。要具備硬件喚醒功能,好歹得是一個能到達CPU Core的中斷纔行,而這中間經過各級中斷管理器,並不說只要是一箇中斷就能喚醒系統的。

那麼,想要具備喚醒系統的功能,就要從中斷上下功夫,如下:

  1. #include <xxx.h>
  2. ...
  3. #include <linux/platform_device.h>
  4. #include <linux/pm_wakeup.h>
  5.  
  6. struct device * dev;
  7.  
  8. int xxx_isr(int irq, void *dev_id)
  9. {
  10. pm_stay_awake(dev);
  11. ....
  12. pm_relax(dev);
  13. return IRQ_HANDLED;
  14. }
  15.  
  16. int xxx_probe(struct platform_device *pdev)
  17. {
  18. int ret;
  19. int irq;
  20. int flag;
  21.  
  22. dev = pdev->dev;
  23. ...
  24. ret = request_irq(irq, xxx_isr, flag | IRQF_NO_SUSPEND, xxx, xxx);
  25. ...
  26. enable_irq_wake(irq);
  27. device_init_wakeup(dev, true);
  28. ...
  29. }
  30.  
  31. int __init xxx_init(void)
  32. {
  33. return platform_driver_register(&xxx_device_driver);
  34. }
  35.  
  36. module_initcall(xxx_init);
  37. 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()之前完成更好。

拋磚引玉,大家多討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章