本文轉自: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中:函數
- int enter_state(suspend_state_t state)
- {
- int error;
-
- if (!valid_state(state))
- return -ENODEV;
-
- if (!mutex_trylock(&pm_mutex))
- return -EBUSY;
-
- printk(KERN_INFO "PM: Syncing filesystems ... ");
- sys_sync();
- printk("done.\n");
-
- pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);
-
- error = suspend_prepare();
- if (error)
- goto Unlock;
-
- if (suspend_test(TEST_FREEZER))
- goto Finish;
-
- pr_debug("PM: Entering %s sleep\n", pm_states[state]);
-
- error = suspend_devices_and_enter(state);
-
- Finish:
- pr_debug("PM: Finishing wakeup.\n");
- suspend_finish();
- Unlock:
- mutex_unlock(&pm_mutex);
- return error;
- }
-
- int pm_suspend(suspend_state_t state)
- {
- if (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX)
- return enter_state(state);
- return -EINVAL;
- }
- EXPORT_SYMBOL(pm_suspend);
在enter_state()中首先進入狀態的判斷,根據平臺的特性判斷是否支持此狀態;而後再同步緩存;接着調用suspend_prepare()凍結大部分進程;而後再經過suspend_devices_and_enter()開始掛起設備。spa
二、凍結進程.net
- static int suspend_prepare(void)
- {
- int error;
-
- if (!suspend_ops || !suspend_ops->enter)
- return -EPERM;
-
- pm_prepare_console();
-
-
- error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
- if (error)
- goto Finish;
-
- error = usermodehelper_disable();
- if (error)
- goto Finish;
-
- error = suspend_freeze_processes();
- if (!error)
- return 0;
-
-
- suspend_thaw_processes();
-
- usermodehelper_enable();
- Finish:
-
- pm_notifier_call_chain(PM_POST_SUSPEND);
- pm_restore_console();
- return error;
- }
這裏有一個notifier機制後面要專門分析下。debug
三、掛起設備rest
- int suspend_devices_and_enter(suspend_state_t state)
- {
- int error;
-
- if (!suspend_ops)
- return -ENOSYS;
-
- if (suspend_ops->begin) {
- error = suspend_ops->begin(state);
- if (error)
- goto Close;
- }
-
- suspend_console();
- suspend_test_start();
-
- error = dpm_suspend_start(PMSG_SUSPEND);
- if (error) {
- printk(KERN_ERR "PM: Some devices failed to suspend\n");
- goto Recover_platform;
- }
- suspend_test_finish("suspend devices");
- if (suspend_test(TEST_DEVICES))
- goto Recover_platform;
-
- suspend_enter(state);
-
- Resume_devices:
- suspend_test_start();
-
- dpm_resume_end(PMSG_RESUME);
- suspend_test_finish("resume devices");
-
- resume_console();
- Close:
-
- if (suspend_ops->end)
- suspend_ops->end();
- return error;
-
- Recover_platform:
- if (suspend_ops->recover)
- suspend_ops->recover();
- goto Resume_devices;
- }
能夠看到設備掛起流程先從處理器自身開始,平臺通常不須要作特殊的處理;接着關閉串口,而後調用dpm_suspend_start()開始掛起設備,若是成功掛起全部設備則調用suspend_enter()掛起處理器。掛起設備部分的代碼在linux/driver/base/power/main.c中orm
- int dpm_suspend_start(pm_message_t state)
- {
- int error;
-
- might_sleep();
- error = dpm_prepare(state);
- if (!error)
- error = dpm_suspend(state);
- return error;
- }
- EXPORT_SYMBOL_GPL(dpm_suspend_start);
掛起設備分爲2個步驟,首先執行設備的prepare函數,而後再執行suspend函數。blog
- static int dpm_prepare(pm_message_t state)
- {
- struct list_head list;
- int error = 0;
-
- INIT_LIST_HEAD(&list);
- mutex_lock(&dpm_list_mtx);
- transition_started = true;
-
- while (!list_empty(&dpm_list)) {
-
- struct device *dev = to_device(dpm_list.next);
-
- get_device(dev);
-
- dev->power.status = DPM_PREPARING;
- mutex_unlock(&dpm_list_mtx);
-
- pm_runtime_get_noresume(dev);
-
- if (pm_runtime_barrier(dev) && device_may_wakeup(dev)) {
-
- pm_runtime_put_noidle(dev);
- error = -EBUSY;
- } else {
- error = device_prepare(dev, state);
- }
-
- mutex_lock(&dpm_list_mtx);
-
- if (error) {
- dev->power.status = DPM_ON;
- if (error == -EAGAIN) {
- put_device(dev);
- error = 0;
- continue;
- }
- printk(KERN_ERR "PM: Failed to prepare device %s "
- "for power transition: error %d\n",
- kobject_name(&dev->kobj), error);
- put_device(dev);
- break;
- }
-
- dev->power.status = DPM_SUSPENDING;
- if (!list_empty(&dev->power.entry))
-
- list_move_tail(&dev->power.entry, &list);
- put_device(dev);
- }
-
- list_splice(&list, &dpm_list);
- mutex_unlock(&dpm_list_mtx);
- return error;
- }
能夠看到函數將遍歷dpm_list鏈表,並執行每一個設備的prepare函數,內核規定prepare函數的實現不能改變硬件的狀態;系統中每個設備註冊時都將被加入dpm_list鏈表的尾部,因此鏈表排序爲設備註冊的順序。
- static int dpm_suspend(pm_message_t state)
- {
- struct list_head list;
- int error = 0;
-
- INIT_LIST_HEAD(&list);
- mutex_lock(&dpm_list_mtx);
- while (!list_empty(&dpm_list)) {
-
- struct device *dev = to_device(dpm_list.prev);
-
- get_device(dev);
- mutex_unlock(&dpm_list_mtx);
-
- dpm_drv_wdset(dev);
- error = device_suspend(dev, state);
- dpm_drv_wdclr(dev);
-
- mutex_lock(&dpm_list_mtx);
- if (error) {
- pm_dev_err(dev, state, "", error);
- put_device(dev);
- break;
- }
- dev->power.status = DPM_OFF;
- if (!list_empty(&dev->power.entry))
- list_move(&dev->power.entry, &list);
- put_device(dev);
- }
- list_splice(&list, dpm_list.prev);
- mutex_unlock(&dpm_list_mtx);
- return error;
- }
函數將設備按照註冊順序反向掛起,掛起執行的流程以下:
- static int device_suspend(struct device *dev, pm_message_t state)
- {
- int error = 0;
-
- down(&dev->sem);
-
- if (dev->class) {
- if (dev->class->pm) {
- pm_dev_dbg(dev, state, "class ");
- error = pm_op(dev, dev->class->pm, state);
- } else if (dev->class->suspend) {
- pm_dev_dbg(dev, state, "legacy class ");
- error = dev->class->suspend(dev, state);
- suspend_report_result(dev->class->suspend, error);
- }
- if (error)
- goto End;
- }
-
- if (dev->type) {
- if (dev->type->pm) {
- pm_dev_dbg(dev, state, "type ");
- error = pm_op(dev, dev->type->pm, state);
- }
- if (error)
- goto End;
- }
-
- if (dev->bus) {
- if (dev->bus->pm) {
- pm_dev_dbg(dev, state, "");
- error = pm_op(dev, dev->bus->pm, state);
- } else if (dev->bus->suspend) {
- pm_dev_dbg(dev, state, "legacy ");
- error = dev->bus->suspend(dev, state);
- suspend_report_result(dev->bus->suspend, error);
- }
- }
- End:
- up(&dev->sem);
-
- return error;
- }
能夠看到類中的suspend優先級最高,以後是device_type的,最後是bus的,大部分設備只註冊了bus下的suspend。
四、掛起處理器
- static int suspend_enter(suspend_state_t state)
- {
- int error;
-
- if (suspend_ops->prepare) {
- error = suspend_ops->prepare();
- if (error)
- return error;
- }
-
- error = dpm_suspend_noirq(PMSG_SUSPEND);
- if (error) {
- printk(KERN_ERR "PM: Some devices failed to power down\n");
- goto Platfrom_finish;
- }
-
- if (suspend_ops->prepare_late) {
- error = suspend_ops->prepare_late();
- if (error)
- goto Power_up_devices;
- }
-
- if (suspend_test(TEST_PLATFORM))
- goto Platform_wake;
-
- error = disable_nonboot_cpus();
- if (error || suspend_test(TEST_CPUS))
- goto Enable_cpus;
-
- arch_suspend_disable_irqs();
- BUG_ON(!irqs_disabled());
-
- error = sysdev_suspend(PMSG_SUSPEND);
- if (!error) {
- if (!suspend_test(TEST_CORE))
-
- error = suspend_ops->enter(state);
-
- sysdev_resume();
- }
-
- arch_suspend_enable_irqs();
- BUG_ON(irqs_disabled());
-
- Enable_cpus:
-
- enable_nonboot_cpus();
-
- Platform_wake:
-
- if (suspend_ops->wake)
- suspend_ops->wake();
-
- Power_up_devices:
-
- dpm_resume_noirq(PMSG_RESUME);
-
- Platfrom_finish:
-
- if (suspend_ops->finish)
- suspend_ops->finish();
-
- return error;
- }
在這個階段首先看處理器是否須要作一些準備,接下來執行非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的實現以下:
- int dpm_suspend_noirq(pm_message_t state)
- {
- struct device *dev;
- int error = 0;
-
- suspend_device_irqs();
- mutex_lock(&dpm_list_mtx);
- list_for_each_entry_reverse(dev, &dpm_list, power.entry) {
-
- error = device_suspend_noirq(dev, state);
- if (error) {
- pm_dev_err(dev, state, " late", error);
- break;
- }
- dev->power.status = DPM_OFF_IRQ;
- }
- mutex_unlock(&dpm_list_mtx);
- if (error)
- dpm_resume_noirq(resume_event(state));
- return error;
- }
- EXPORT_SYMBOL_GPL(dpm_suspend_noirq);
附二、中斷關閉流程
在late suspend機制中咱們看到了休眠流程中關閉系統中斷的地方:
- void suspend_device_irqs(void)
- {
- struct irq_desc *desc;
- int irq;
-
- for_each_irq_desc(irq, desc) {
- unsigned long flags;
-
- spin_lock_irqsave(&desc->lock, flags);
- __disable_irq(desc, irq, true);
- spin_unlock_irqrestore(&desc->lock, flags);
- }
-
- for_each_irq_desc(irq, desc)
- if (desc->status & IRQ_SUSPENDED)
- synchronize_irq(irq);
- }
- EXPORT_SYMBOL_GPL(suspend_device_irqs);
函數調用了__disable_irq()來關閉中斷,咱們看一下這個函數的實現:
- void __disable_irq(struct irq_desc *desc, unsigned int irq, bool suspend)
- {
- if (suspend) {
-
-
-
- if (!desc->action || (desc->action->flags & IRQF_TIMER))
- return;
- desc->status |= IRQ_SUSPENDED;
- }
-
- if (!desc->depth++) {
-
- desc->status |= IRQ_DISABLED;
-
- desc->chip->disable(irq);
- }
- }
能夠看到若是該中斷沒有被激活或者中斷的IRQF_TIMER標誌被置位就不會關閉中斷,在新的內核版本中增長了專門的 IRQF_NO_SUSPEND 標誌位,用來置位在休眠狀態下喚醒系統的中斷,如RTC、按鍵等;若是是其餘中斷則將打開的中斷關閉掉。
附三、dpm_list鏈表
dpm_list是內核中用於設備電源管理的鏈表,設備註冊時經過一系列的調用 device_register() -> device_add() -> device_pm_add() 最後在device_pm_add()中將設備加入dpm_list鏈表中:
- void device_pm_add(struct device *dev)
- {
- pr_debug("PM: Adding info for %s:%s\n",
- dev->bus ? dev->bus->name : "No Bus",
- kobject_name(&dev->kobj));
- mutex_lock(&dpm_list_mtx);
- if (dev->parent) {
- if (dev->parent->power.status >= DPM_SUSPENDING)
- dev_warn(dev, "parent %s should not be sleeping\n",
- dev_name(dev->parent));
- } else if (transition_started) {
-
- dev_WARN(dev, "Parentless device registered during a PM transaction\n");
- }
-
- list_add_tail(&dev->power.entry, &dpm_list);
- mutex_unlock(&dpm_list_mtx);
- }
而設備註銷的時候會調用device_pm_remove()將設備從dpm_list鏈表中移除:
- void device_pm_remove(struct device *dev)
- {
- pr_debug("PM: Removing info for %s:%s\n",
- dev->bus ? dev->bus->name : "No Bus",
- kobject_name(&dev->kobj));
- mutex_lock(&dpm_list_mtx);
- list_del_init(&dev->power.entry);
- mutex_unlock(&dpm_list_mtx);
- pm_runtime_remove(dev);
- }