android 休眠喚醒機制分析(三) — suspend

本文轉自:http://blog.csdn.net/g_salamander/article/details/7988340linux

前面咱們分析了休眠的第一個階段即淺度休眠,如今咱們繼續看休眠的第二個階段 — 深度休眠。在深度休眠的過程當中系統會首先凍結全部能夠凍結的進程,而後依次掛起全部設備的電源,掛起順序與設備註冊的順序相反,這樣保證了設備之間電源的依賴性;直至最後進入省電模式,等待用戶或者RTC喚醒;在喚醒過程當中則會按照設備註冊的順序依次恢復每一個設備的電源進入正常工做狀態,解凍相關的進程,而後再進行淺度休眠的喚醒流程。緩存

一、深度休眠入口less

根據wake_lock一節的分析咱們知道driver層進入深度休眠的入口有4個,分別爲expire_timer、wake_lock、wake_lock_timeout、wake_unlock,這幾個入口函數將根據相應的條件啓動suspend_work裏面的pm_suspend()函數進入深度休眠流程,代碼在linux/kernel/power/suspend.c中:函數

 

[cpp]  view plain copy
 
  1. // 進入深度休眠流程  
  2. int enter_state(suspend_state_t state)  
  3. {  
  4.     int error;  
  5.     // 判斷平臺是否支持該狀態  
  6.     if (!valid_state(state))  
  7.         return -ENODEV;  
  8.   
  9.     if (!mutex_trylock(&pm_mutex))  
  10.         return -EBUSY;  
  11.     // 同步緩存  
  12.     printk(KERN_INFO "PM: Syncing filesystems ... ");  
  13.     sys_sync();  
  14.     printk("done.\n");  
  15.   
  16.     pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);  
  17.     // 作好休眠準備  
  18.     error = suspend_prepare();  
  19.     if (error)  
  20.         goto Unlock;  
  21.     // suspend_test  
  22.     if (suspend_test(TEST_FREEZER))  
  23.         goto Finish;  
  24.   
  25.     pr_debug("PM: Entering %s sleep\n", pm_states[state]);  
  26.     // 設備休眠  
  27.     error = suspend_devices_and_enter(state);  
  28.   
  29.  Finish:  
  30.     pr_debug("PM: Finishing wakeup.\n");  
  31.     suspend_finish();  
  32.  Unlock:  
  33.     mutex_unlock(&pm_mutex);  
  34.     return error;  
  35. }  
  36.   
  37. int pm_suspend(suspend_state_t state)  
  38. {  
  39.     if (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX)  
  40.         return enter_state(state);  
  41.     return -EINVAL;  
  42. }  
  43. EXPORT_SYMBOL(pm_suspend);  

在enter_state()中首先進入狀態的判斷,根據平臺的特性判斷是否支持此狀態;而後再同步緩存;接着調用suspend_prepare()凍結大部分進程;而後再經過suspend_devices_and_enter()開始掛起設備。spa

二、凍結進程.net

[cpp]  view plain copy
 
  1. static int suspend_prepare(void)  
  2. {  
  3.     int error;  
  4.   
  5.     if (!suspend_ops || !suspend_ops->enter)  
  6.         return -EPERM;  
  7.   
  8.     pm_prepare_console();  
  9.   
  10.     // 通知進行休眠準備  
  11.     error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);  
  12.     if (error)  
  13.         goto Finish;  
  14.     // 禁止usermodehelper  
  15.     error = usermodehelper_disable();  
  16.     if (error)  
  17.         goto Finish;  
  18.     // 凍結全部能夠凍結的進程  
  19.     error = suspend_freeze_processes();  
  20.     if (!error)  
  21.         return 0;  
  22.   
  23.     // 解凍全部進程  
  24.     suspend_thaw_processes();  
  25.     // 使能usermodehelper  
  26.     usermodehelper_enable();  
  27.  Finish:  
  28.     // 通知休眠結束  
  29.     pm_notifier_call_chain(PM_POST_SUSPEND);  
  30.     pm_restore_console();  
  31.     return error;  
  32. }  

這裏有一個notifier機制後面要專門分析下。debug

三、掛起設備rest

 

[cpp]  view plain copy
 
  1. int suspend_devices_and_enter(suspend_state_t state)  
  2. {  
  3.     int error;  
  4.   
  5.     if (!suspend_ops)  
  6.         return -ENOSYS;  
  7.     // 處理器的休眠開始函數  
  8.     if (suspend_ops->begin) {  
  9.         error = suspend_ops->begin(state);  
  10.         if (error)  
  11.             goto Close;  
  12.     }  
  13.     // 休眠串口  
  14.     suspend_console();  
  15.     suspend_test_start();  
  16.     // 設備休眠  
  17.     error = dpm_suspend_start(PMSG_SUSPEND);  
  18.     if (error) {  
  19.         printk(KERN_ERR "PM: Some devices failed to suspend\n");  
  20.         goto Recover_platform;  
  21.     }  
  22.     suspend_test_finish("suspend devices");  
  23.     if (suspend_test(TEST_DEVICES))  
  24.         goto Recover_platform;  
  25.     // 處理器休眠  
  26.     suspend_enter(state);  
  27.   
  28.  Resume_devices:  
  29.     suspend_test_start();  
  30.     // 設備喚醒  
  31.     dpm_resume_end(PMSG_RESUME);  
  32.     suspend_test_finish("resume devices");  
  33.     // 喚醒串口  
  34.     resume_console();  
  35.  Close:  
  36.     // 處理器的休眠結束函數  
  37.     if (suspend_ops->end)  
  38.         suspend_ops->end();  
  39.     return error;  
  40.   
  41.  Recover_platform:  
  42.     if (suspend_ops->recover)  
  43.         suspend_ops->recover();  
  44.     goto Resume_devices;  
  45. }  

能夠看到設備掛起流程先從處理器自身開始,平臺通常不須要作特殊的處理;接着關閉串口,而後調用dpm_suspend_start()開始掛起設備,若是成功掛起全部設備則調用suspend_enter()掛起處理器。掛起設備部分的代碼在linux/driver/base/power/main.c中orm

 

[cpp]  view plain copy
 
  1. int dpm_suspend_start(pm_message_t state)  
  2. {  
  3.     int error;  
  4.   
  5.     might_sleep();  
  6.     error = dpm_prepare(state);  
  7.     if (!error)  
  8.         error = dpm_suspend(state);  
  9.     return error;  
  10. }  
  11. EXPORT_SYMBOL_GPL(dpm_suspend_start);  

掛起設備分爲2個步驟,首先執行設備的prepare函數,而後再執行suspend函數。blog

[cpp]  view plain copy
 
  1. // 函數將會調用全部的非sysdev設備的prepare()接口  
  2. static int dpm_prepare(pm_message_t state)  
  3. {  
  4.     struct list_head list;  
  5.     int error = 0;  
  6.   
  7.     INIT_LIST_HEAD(&list);  
  8.     mutex_lock(&dpm_list_mtx);  
  9.     transition_started = true;  
  10.     // 遍歷設備鏈表  
  11.     while (!list_empty(&dpm_list)) {  
  12.         // 從最早初始化的節點開始遍歷  
  13.         struct device *dev = to_device(dpm_list.next);  
  14.         // 獲取設備  
  15.         get_device(dev);  
  16.         // 更新設備狀態  
  17.         dev->power.status = DPM_PREPARING;  
  18.         mutex_unlock(&dpm_list_mtx);  
  19.   
  20.         pm_runtime_get_noresume(dev);  
  21.         // 在系統休眠期間有可能受到喚醒請求  
  22.         if (pm_runtime_barrier(dev) && device_may_wakeup(dev)) {  
  23.             /* Wake-up requested during system sleep transition. */  
  24.             pm_runtime_put_noidle(dev);  
  25.             error = -EBUSY;  
  26.         } else {  // 執行prepare()函數  
  27.             error = device_prepare(dev, state);  
  28.         }  
  29.   
  30.         mutex_lock(&dpm_list_mtx);  
  31.         // 若是出錯則跳出循環  
  32.         if (error) {  
  33.             dev->power.status = DPM_ON;  
  34.             if (error == -EAGAIN) {  
  35.                 put_device(dev);  
  36.                 error = 0;  
  37.                 continue;  
  38.             }  
  39.             printk(KERN_ERR "PM: Failed to prepare device %s "  
  40.                 "for power transition: error %d\n",  
  41.                 kobject_name(&dev->kobj), error);  
  42.             put_device(dev);  
  43.             break;  
  44.         }  
  45.         // 更新狀態  
  46.         dev->power.status = DPM_SUSPENDING;  
  47.         if (!list_empty(&dev->power.entry))  
  48.             // 將設備節點移動到list鏈表中  
  49.             list_move_tail(&dev->power.entry, &list);  
  50.         put_device(dev);  
  51.     }  
  52.     // 拼接鏈表  
  53.     list_splice(&list, &dpm_list);  
  54.     mutex_unlock(&dpm_list_mtx);  
  55.     return error;  
  56. }  

能夠看到函數將遍歷dpm_list鏈表,並執行每一個設備的prepare函數,內核規定prepare函數的實現不能改變硬件的狀態;系統中每個設備註冊時都將被加入dpm_list鏈表的尾部,因此鏈表排序爲設備註冊的順序。

[cpp]  view plain copy
 
  1. static int dpm_suspend(pm_message_t state)  
  2. {  
  3.     struct list_head list;  
  4.     int error = 0;  
  5.   
  6.     INIT_LIST_HEAD(&list);  
  7.     mutex_lock(&dpm_list_mtx);  
  8.     while (!list_empty(&dpm_list)) {  
  9.         // 逆序遍歷鏈表,即先suspend後註冊的設備,符合設備與父設備電源掛起的前後原則  
  10.         struct device *dev = to_device(dpm_list.prev);  
  11.   
  12.         get_device(dev);  
  13.         mutex_unlock(&dpm_list_mtx);  
  14.   
  15.         dpm_drv_wdset(dev);  
  16.         error = device_suspend(dev, state);  
  17.         dpm_drv_wdclr(dev);  
  18.   
  19.         mutex_lock(&dpm_list_mtx);  
  20.         if (error) {  
  21.             pm_dev_err(dev, state, "", error);  
  22.             put_device(dev);  
  23.             break;  
  24.         }  
  25.         dev->power.status = DPM_OFF;  
  26.         if (!list_empty(&dev->power.entry))  
  27.             list_move(&dev->power.entry, &list);  
  28.         put_device(dev);  
  29.     }  
  30.     list_splice(&list, dpm_list.prev);  
  31.     mutex_unlock(&dpm_list_mtx);  
  32.     return error;  
  33. }  

函數將設備按照註冊順序反向掛起,掛起執行的流程以下:

[cpp]  view plain copy
 
  1. static int device_suspend(struct device *dev, pm_message_t state)  
  2. {  
  3.     int error = 0;  
  4.   
  5.     down(&dev->sem);  
  6.   
  7.     if (dev->class) {  // 類的suspend優先  
  8.         if (dev->class->pm) {  
  9.             pm_dev_dbg(dev, state, "class ");  
  10.             error = pm_op(dev, dev->class->pm, state);  
  11.         } else if (dev->class->suspend) {  
  12.             pm_dev_dbg(dev, state, "legacy class ");  
  13.             error = dev->class->suspend(dev, state);  
  14.             suspend_report_result(dev->class->suspend, error);  
  15.         }  
  16.         if (error)  
  17.             goto End;  
  18.     }  
  19.   
  20.     if (dev->type) {  // device_type次之  
  21.         if (dev->type->pm) {  
  22.             pm_dev_dbg(dev, state, "type ");  
  23.             error = pm_op(dev, dev->type->pm, state);  
  24.         }  
  25.         if (error)  
  26.             goto End;  
  27.     }  
  28.   
  29.     if (dev->bus) {  // bus優先級最低  
  30.         if (dev->bus->pm) {  
  31.             pm_dev_dbg(dev, state, "");  
  32.             error = pm_op(dev, dev->bus->pm, state);  
  33.         } else if (dev->bus->suspend) {  
  34.             pm_dev_dbg(dev, state, "legacy ");  
  35.             error = dev->bus->suspend(dev, state);  
  36.             suspend_report_result(dev->bus->suspend, error);  
  37.         }  
  38.     }  
  39.  End:  
  40.     up(&dev->sem);  
  41.   
  42.     return error;  
  43. }  

能夠看到類中的suspend優先級最高,以後是device_type的,最後是bus的,大部分設備只註冊了bus下的suspend。
四、掛起處理器

 

[cpp]  view plain copy
 
  1. static int suspend_enter(suspend_state_t state)  
  2. {  
  3.     int error;  
  4.     // 處理器的休眠準備函數  
  5.     if (suspend_ops->prepare) {  
  6.         error = suspend_ops->prepare();  
  7.         if (error)  
  8.             return error;  
  9.     }  
  10.     // 執行非sysdev的late suspend函數  
  11.     error = dpm_suspend_noirq(PMSG_SUSPEND);  
  12.     if (error) {  
  13.         printk(KERN_ERR "PM: Some devices failed to power down\n");  
  14.         goto Platfrom_finish;  
  15.     }  
  16.     // 處理器休眠最後的準備  
  17.     if (suspend_ops->prepare_late) {  
  18.         error = suspend_ops->prepare_late();  
  19.         if (error)  
  20.             goto Power_up_devices;  
  21.     }  
  22.   
  23.     if (suspend_test(TEST_PLATFORM))  
  24.         goto Platform_wake;  
  25.     // 關閉非啓動cpu  
  26.     error = disable_nonboot_cpus();  
  27.     if (error || suspend_test(TEST_CPUS))  
  28.         goto Enable_cpus;  
  29.     // 掛起中斷  
  30.     arch_suspend_disable_irqs();  
  31.     BUG_ON(!irqs_disabled());  
  32.     // 掛起sysdev  
  33.     error = sysdev_suspend(PMSG_SUSPEND);  
  34.     if (!error) {  
  35.         if (!suspend_test(TEST_CORE))  
  36.             // 處理器的休眠進入函數,休眠流程運行至此  
  37.             error = suspend_ops->enter(state);  
  38.         // 喚醒sysdev  
  39.         sysdev_resume();  
  40.     }  
  41.     // 使能中斷  
  42.     arch_suspend_enable_irqs();  
  43.     BUG_ON(irqs_disabled());  
  44.   
  45.  Enable_cpus:  
  46.     // 使能非啓動cpu  
  47.     enable_nonboot_cpus();  
  48.   
  49.  Platform_wake:  
  50.     // 處理器開始喚醒  
  51.     if (suspend_ops->wake)  
  52.         suspend_ops->wake();  
  53.   
  54.  Power_up_devices:  
  55.     // 執行非sysdev的early resume函數  
  56.     dpm_resume_noirq(PMSG_RESUME);  
  57.   
  58.  Platfrom_finish:  
  59.     // 處理器休眠結束  
  60.     if (suspend_ops->finish)  
  61.         suspend_ops->finish();  
  62.   
  63.     return error;  
  64. }  

在這個階段首先看處理器是否須要作一些準備,接下來執行非sysdev的late suspend函數,而後處理器作休眠前最後的準備、關閉非啓動cpu、掛起中斷,再掛起sysdev,最後進入處理器的掛起函數,至此休眠流程結束,處理器等待用戶或者RTC喚醒。

附一、late suspend

在這裏咱們看到了一種新的suspend機制 — late suspend,是在全部的suspend執行完後再開始執行,接口爲dev->bus->pm->suspend_noirq;這樣early_suspend、suspend以及late suspend構成了suspend的三部曲,late suspend是在中斷關閉的狀況下進行的;前面咱們分析的wake_lock就有用到,用於檢測在suspend階段是否有鎖被激活。late suspend的實現以下:

 

 

[cpp]  view plain copy
 
  1. int dpm_suspend_noirq(pm_message_t state)  
  2. {  
  3.     struct device *dev;  
  4.     int error = 0;  
  5.   
  6.     suspend_device_irqs();  // 關閉除喚醒系統之外的全部中斷  
  7.     mutex_lock(&dpm_list_mtx);  
  8.     list_for_each_entry_reverse(dev, &dpm_list, power.entry) {  
  9.         // 執行全部設備的late suspend函數  
  10.         error = device_suspend_noirq(dev, state);  
  11.         if (error) {  
  12.             pm_dev_err(dev, state, " late", error);  
  13.             break;  
  14.         }  
  15.         dev->power.status = DPM_OFF_IRQ;  
  16.     }  
  17.     mutex_unlock(&dpm_list_mtx);  
  18.     if (error)  
  19.         dpm_resume_noirq(resume_event(state));  
  20.     return error;  
  21. }  
  22. EXPORT_SYMBOL_GPL(dpm_suspend_noirq);  

 

附二、中斷關閉流程

在late suspend機制中咱們看到了休眠流程中關閉系統中斷的地方:

 

[cpp]  view plain copy
 
  1. void suspend_device_irqs(void)  
  2. {  
  3.     struct irq_desc *desc;  
  4.     int irq;  
  5.   
  6.     for_each_irq_desc(irq, desc) {  // 遍歷系統的中斷  
  7.         unsigned long flags;  
  8.   
  9.         spin_lock_irqsave(&desc->lock, flags);  
  10.         __disable_irq(desc, irq, true);  // 關閉中斷  
  11.         spin_unlock_irqrestore(&desc->lock, flags);  
  12.     }  
  13.   
  14.     for_each_irq_desc(irq, desc)  
  15.         if (desc->status & IRQ_SUSPENDED)  
  16.             synchronize_irq(irq);  
  17. }  
  18. EXPORT_SYMBOL_GPL(suspend_device_irqs);  

函數調用了__disable_irq()來關閉中斷,咱們看一下這個函數的實現:

 

 

[cpp]  view plain copy
 
  1. void __disable_irq(struct irq_desc *desc, unsigned int irq, bool suspend)  
  2. {  
  3.     if (suspend) {  
  4.         // 若是中斷沒有被激活或者中斷的IRQF_TIMER標誌被置位則不關閉中斷  
  5.         // 在之後的內核版本中這個標誌位被換成了IRQF_NO_SUSPEND  
  6.         // 新版的IRQF_TIMER = (__IRQF_TIMER | IRQF_NO_SUSPEND)  
  7.         if (!desc->action || (desc->action->flags & IRQF_TIMER))  
  8.             return;  
  9.         desc->status |= IRQ_SUSPENDED;  
  10.     }  
  11.     // 判斷中斷是否被打開  
  12.     if (!desc->depth++) {  
  13.         // 更新標誌位  
  14.         desc->status |= IRQ_DISABLED;  
  15.         // 關閉中斷  
  16.         desc->chip->disable(irq);  
  17.     }  
  18. }  

能夠看到若是該中斷沒有被激活或者中斷的IRQF_TIMER標誌被置位就不會關閉中斷,在新的內核版本中增長了專門的 IRQF_NO_SUSPEND 標誌位,用來置位在休眠狀態下喚醒系統的中斷,如RTC、按鍵等;若是是其餘中斷則將打開的中斷關閉掉。

附三、dpm_list鏈表

dpm_list是內核中用於設備電源管理的鏈表,設備註冊時經過一系列的調用 device_register() -> device_add() -> device_pm_add() 最後在device_pm_add()中將設備加入dpm_list鏈表中:

 

[cpp]  view plain copy
 
  1. // 設備建立時都會調用的函數,將設備加入dpm_list鏈表  
  2. void device_pm_add(struct device *dev)  
  3. {  
  4.     pr_debug("PM: Adding info for %s:%s\n",  
  5.          dev->bus ? dev->bus->name : "No Bus",  
  6.          kobject_name(&dev->kobj));  
  7.     mutex_lock(&dpm_list_mtx);  
  8.     if (dev->parent) {  
  9.         if (dev->parent->power.status >= DPM_SUSPENDING)  
  10.             dev_warn(dev, "parent %s should not be sleeping\n",  
  11.                  dev_name(dev->parent));  
  12.     } else if (transition_started) {  
  13.         /* 
  14.          * We refuse to register parentless devices while a PM 
  15.          * transition is in progress in order to avoid leaving them 
  16.          * unhandled down the road 
  17.          */  
  18.         dev_WARN(dev, "Parentless device registered during a PM transaction\n");  
  19.     }  
  20.     // 將設備節點添加到鏈表尾部,即設備按註冊的前後順序從鏈表頭部到尾部  
  21.     list_add_tail(&dev->power.entry, &dpm_list);  
  22.     mutex_unlock(&dpm_list_mtx);  
  23. }  

而設備註銷的時候會調用device_pm_remove()將設備從dpm_list鏈表中移除:

 

 

[cpp]  view plain copy
 
    1. // 設備註銷時都會調用的函數,將設備從dpm_list鏈表中移除  
    2. void device_pm_remove(struct device *dev)  
    3. {  
    4.     pr_debug("PM: Removing info for %s:%s\n",  
    5.          dev->bus ? dev->bus->name : "No Bus",  
    6.          kobject_name(&dev->kobj));  
    7.     mutex_lock(&dpm_list_mtx);  
    8.     list_del_init(&dev->power.entry);  
    9.     mutex_unlock(&dpm_list_mtx);  
    10.     pm_runtime_remove(dev);  
    11. }  
相關文章
相關標籤/搜索