syscore

struct syscore_ops裏有四個成員,一個list,三個函數指針(suspend, resume and shutdown)。模塊如果需要向syscore註冊,先定義一個struct syscore_ops型變量(可只給部分函數指針賦值),然後使用register_syscore_ops進行註冊,反之也可用unregister_syscore_ops解除註冊。

syscore_suspend/syscore_resume 在系統的suspend/resume 中被調用,syscore_shutdown 在系統關機或者重啓時被調用,對模塊的工程師來說,知道它們會被調用就行了,如果是負責suspend/resume/shutdown/reboot,需要了解它們什麼時候會被調用。

個人感覺可把一些重要的、需要晚於device進行suspend或者shutdown的動作,放到syscore中;或者把需要早於device進行resume的動作,放到syscore中。


kernel syscore

http://blog.csdn.net/lixiaojie1012/article/details/45625699

syscore作爲低功耗流程的一部分,其涉及的文件主要有syscore_ops.h和syscore.c,這一級別的回調函數是在完全屏蔽中斷的場景下進行的。

1、主要結構體struct syscore_ops,該結構體是syscore操作的對象類型。

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. struct syscore_ops {  
  2.     struct list_head node;//用於鏈表控制,註冊和刪除syscore對象時操作此鏈表完成  
  3.     int (*suspend)(void);//睡眠流程時回調函數  
  4.     void (*resume)(void);//喚醒流程時回調函數  
  5.     void (*shutdown)(void);//這一級別的回調函數主要用於系統級的重啓、停止或者掉電時纔會使用  
  6. };  

2、主要接口:

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. extern void register_syscore_ops(struct syscore_ops *ops);//註冊syscore回調函數  
  2. extern void unregister_syscore_ops(struct syscore_ops *ops);//取消註冊的回調函數  
  3. #ifdef CONFIG_PM_SLEEP  
  4. extern int syscore_suspend(void);//回調註冊的syscore對象的suspend回調函數  
  5. extern void syscore_resume(void);//回調註冊的syscore對象的resume回調函數  
  6. #endif  
  7. extern void syscore_shutdown(void);//回調註冊的syscore對象的shutdown回調函數  

3、具體實現,syscore.c

1)全局變量

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. static LIST_HEAD(syscore_ops_list);//初始化控制鏈表頭結點,統一管理註冊的syscore對象  
  2. static DEFINE_MUTEX(syscore_ops_lock);//訪問上述鏈表時,通過此互斥信號量來互斥訪問  

2)register_syscore_ops:其他組件主要通過此接口來註冊回調函數,我們可以看到,把註冊的對象存放在了syscore_ops_list鏈表中。

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.  * register_syscore_ops - Register a set of system core operations. 
  3.  * @ops: System core operations to register. 
  4.  */  
  5. void register_syscore_ops(struct syscore_ops *ops)  
  6. {  
  7.     mutex_lock(&syscore_ops_lock);  
  8.     list_add_tail(&ops->node, &syscore_ops_list);  
  9.     mutex_unlock(&syscore_ops_lock);  
  10. }  

3)unregister_syscore_ops:與register_syscore_ops功能相反,取消註冊,從控制鏈表中刪除。

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.  * unregister_syscore_ops - Unregister a set of system core operations. 
  3.  * @ops: System core operations to unregister. 
  4.  */  
  5. void unregister_syscore_ops(struct syscore_ops *ops)  
  6. {  
  7.     mutex_lock(&syscore_ops_lock);  
  8.     list_del(&ops->node);  
  9.     mutex_unlock(&syscore_ops_lock);  
  10. }  

4)syscore_suspend:該接口回調所有註冊對象的suspend接口,該接口在suspend.c的suspend_enter函數中被調用,執行cpu掉電前的最後階段操作。

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.  * syscore_suspend - Execute all the registered system core suspend callbacks. 
  3.  * 
  4.  * This function is executed with one CPU on-line and disabled interrupts. 
  5.  */  
  6. int syscore_suspend(void)  
  7. {  
  8.     struct syscore_ops *ops;  
  9.     int ret = 0;  
  10.   
  11.     pr_debug("Checking wakeup interrupts\n");  
  12.   
  13.     /* Return error code if there are any wakeup interrupts pending. */  
  14.     ret = check_wakeup_irqs();  
  15.     if (ret)  
  16.         return ret;  
  17.   
  18.     WARN_ONCE(!irqs_disabled(),  
  19.         "Interrupts enabled before system core suspend.\n");//我們可以看到,如果此時中斷沒有屏蔽掉,會有警告產生  
  20.   
  21.     list_for_each_entry_reverse(ops, &syscore_ops_list, node)//按照鏈表逆序執行各個註冊對象的suspend回調函數  
  22.         if (ops->suspend) {  
  23.             if (initcall_debug)  
  24.                 pr_info("PM: Calling %pF\n", ops->suspend);  
  25.             ret = ops->suspend();  
  26.             if (ret)  
  27.                 goto err_out;  
  28.             WARN_ONCE(!irqs_disabled(),  
  29.                 "Interrupts enabled after %pF\n", ops->suspend);  
  30.         }  
  31.   
  32.     return 0;  
  33.   
  34.  err_out:  
  35.     pr_err("PM: System core suspend callback %pF failed.\n", ops->suspend);  
  36.   
  37.     list_for_each_entry_continue(ops, &syscore_ops_list, node)//如果有失敗的,則執行已經執行過suspend回調的對象的resume回調  
  38.         if (ops->resume)  
  39.             ops->resume();  
  40.   
  41.     return ret;  
  42. }  
  43. EXPORT_SYMBOL_GPL(syscore_suspend);  

5)syscore_resume:執行註冊對象的resume回調,在suspend.c的喚醒流程中被調用

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.  * syscore_resume - Execute all the registered system core resume callbacks. 
  3.  * 
  4.  * This function is executed with one CPU on-line and disabled interrupts. 
  5.  */  
  6. void syscore_resume(void)  
  7. {  
  8.     struct syscore_ops *ops;  
  9.   
  10.     WARN_ONCE(!irqs_disabled(),  
  11.         "Interrupts enabled before system core resume.\n");  
  12.   
  13.     list_for_each_entry(ops, &syscore_ops_list, node)  
  14.         if (ops->resume) {  
  15.             if (initcall_debug)  
  16.                 pr_info("PM: Calling %pF\n", ops->resume);  
  17.             ops->resume();  
  18.             WARN_ONCE(!irqs_disabled(),  
  19.                 "Interrupts enabled after %pF\n", ops->resume);  
  20.         }  
  21. }  


6)syscore_shutdown:該接口正常睡眠流程中不涉及調用,主要在sys.c中調用,涉及系統重啓、halt等流程。

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.  * syscore_shutdown - Execute all the registered system core shutdown callbacks. 
  3.  */  
  4. void syscore_shutdown(void)  
  5. {  
  6.     struct syscore_ops *ops;  
  7.   
  8.     mutex_lock(&syscore_ops_lock);  
  9.   
  10.     list_for_each_entry_reverse(ops, &syscore_ops_list, node)  
  11.         if (ops->shutdown) {  
  12.             if (initcall_debug)  
  13.                 pr_info("PM: Calling %pF\n", ops->shutdown);  
  14.             ops->shutdown();  
  15.         }  
  16.   
  17.     mutex_unlock(&syscore_ops_lock);  
  18. }  

Linux設備驅動suspend/resume的順序

http://markzhang.cn/blog/2014/04/09/linux-driver-suspend-orders/

Linux在進入suspend的時候,會首先freeze userspace,然後挨個去調用設備驅動程序的suspend,最後調用architecture dependent的callback(非常奔放的描述了,其實有大量的細節問題存在)。Resume就是反過來執行一遍,最後重新enable userspace。那麼作爲設備驅動程序的編寫者來說,主要就是要實現本驅動的suspend/resume邏輯。但是事實上,很多設備之間是有依賴性的,比如在ARM上,很多設備在resume之前,要求 IOMMU 要已經被enable,否則硬件上就會出現問題,因爲訪問的地址不對了。

那麼Linux是如何設計/定義各個設備驅動之間的suspend/resume的順序的呢?

簡單來說,就是以下幾條邏輯:

  • 各設備驅動程序按照其對應的設備(device)註冊的先後次序,來決定其suspend/resume的順序。注意是該驅動對應的設備的註冊順序,不是驅動註冊的順序,所以和驅動的註冊優先級無關(也就是和module_init, rootfs_init, subsys_initcall這些無關)。越早被註冊的設備,其對應的驅動的suspend越晚被調用,其對應驅動的resume越早被調用;越晚被註冊的設備,其對應的驅動的suspend越早被調用,其對應的驅動的resume越晚被調用。

  • 上面所說的邏輯往往不能滿足我們的要求,因爲現在的設備(device)都通過DT(Device Tree)的方式進行註冊。那麼,在DTS文件中,出現的越早的設備就越早被註冊(可以參考函數 of_platform_populate)。而按照現在upstream linux kernel的約定,設備在DTS文件中,是按照其寄存器的地址,從低到高排序的,所以自然不可能滿足我們的需求。

那要怎麼辦?答案是驅動程序執行probe的時候返回 -EPROBE_DEFER 。

對於現在ARM SoC上的設備和其驅動程序來說,Linux kernel都使用Platform device/Platform driver來描述他們。而Platform driver的probe函數就相當於是驅動的初始化函數,一般用來映射寄存器,分配必需的內存等等,看一個驅動程序的代碼,一般也可以從Probe開始看起。

Probe函數返回 -EPROBE_DEFER,指的是當一個設備驅動程序檢測到其依賴的設備的驅動程序還沒有ready的時候,返回這個出錯碼告訴kernel driver framework,我需要延緩自身的probe。還是舉上面那個IOMMU的例子,如果一個設備依賴IOMMU,那麼該設備的驅動在probe的時候,就應該檢測iommu的驅動是否已經ready,如果沒有那就要defer自己的probe。在Linux kernel driver framework中,收到驅動probe返回 -EPROBE_DEFER,就會將該驅動放入一個list中,一個workqueue來負責遍歷這個list,從而再次執行這些驅動的probe函數(事實上,由於kernel driver framework不知道驅動互相之間的依賴關係,所以一旦有一個驅動成功probe了,kernel都會觸發這個workqueue,讓其去遍歷一遍list)。

上面說的是 -EPROBE_DEFER 的背景故事了,而事實上,當一個驅動的probe返回過 -EPROBE_DEFER ,而最終成功probe之後,kernel就會修改該驅動的suspend/resume的順序了。這就是爲什麼驅動probe的時候返回 -EPROBE_DEFER 能影響其suspend/resume的次序的原因了:

1
2
3
4
5
6
7
8
9
/*
 * Force the device to the end of the dpm_list since
 * the PM code assumes that the order we add things to
 * the list is a good order for suspend but deferred
 * probe makes that very unsafe.
 */
device_pm_lock();
device_pm_move_last(dev);
device_pm_unlock();

上面的代碼來自函數:deferred_probe_work_func,也就是上面所說的defer probe的workqueue的工作函數。所以這裏可以看到,當一個驅動最終在probe defer的work函數中probe成功之後,kernel會修改該驅動對應的設備(device)在 dpm_list 中的位置,而 dpm_list 正是kernel suspend/resume 驅動時候遍歷的鏈表。看到調用了函數:device_pm_move_last(dev); 沒?這個設備被移到了 dpm_list 的末尾。

  • 事情到此爲止,可以說已經有了解決方案:我們可以使用 DEFER PROBE 的方式來調整驅動 suspend/resume 的順序。不過這裏要提一下,還有一條路也是可行的,那就是利用 syscore suspend/resume

syscore是Linux kernel定義的一個framework,我們可以將我們的驅動註冊到syscore中去。syscore比較關鍵的就是 syscore_ops 這個結構:

1
2
3
4
5
6
struct syscore_ops {
  struct list_head node;
  int (*suspend)(void);
  void (*resume)(void);
  void (*shutdown)(void);
};

可以看到只有 suspend/resume/shutdown 這三個ops,所以看到這裏大概也可以猜到了,註冊到syscore中的驅動,其suspend函數是在所有其他驅動的suspend都調用過之後調用的,而其resume函數是在所有其他驅動的resume調用之前調用的。簡單來說,syscore的驅動,suspend被晚調用保證其他驅動suspend的時候,syscore的驅動還活着;syscore的驅動,resume被很早調用,從而保證其他驅動resume的時候,syscore的驅動已經活着了。好繞。。。

要將驅動註冊到syscore中很簡單,調用函數:register_syscore_ops(&your_driver_syscore_ops); 就可以了。至於上面說到的syscore的suspend/resume的順序,實現代碼在這裏:

1
2
3
4
5
6
7
8
9
error = syscore_suspend();
if (!error) {
  *wakeup = pm_wakeup_pending();
  if (!(suspend_test(TEST_CORE) || *wakeup)) {
      error = suspend_ops->enter(state);
      events_check_enabled = false;
  }
  syscore_resume();
}

以上代碼來自函數:suspend_enter。可以看到當syscore_suspend被調用之後,就調用architecture dependent的 suspend_ops->enter 了,到這裏整個系統就已經停下來了,進入suspend狀態了。而當resume開始,suspend_ops->enter 返回之後,第一個被調用的就是 syscore_resume,所以上面那一段聽起來好繞的邏輯就這麼區區幾行代碼解釋了。

版權所有 ©2016 轉載本站文章請註明: 轉載自 http://markzhang.cn
原文鏈接: http://www.markzhang.cn/blog/2014/04/09/linux-driver-suspend-orders/
您可以隨意地轉載本站的文章,但是必須在醒目位置註明來源及本站鏈接,不可以將本站文章商業化使用,或者修改、轉換或者以本作品爲基礎進行創作。


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