前面我们分析了休眠的第一个阶段即浅度休眠,现在我们继续看休眠的第二个阶段 — 深度休眠。在深度休眠的过程中系统会首先冻结所有可以冻结的进程,然后依次挂起所有设备的电源,挂起顺序与设备注册的顺序相反,这样保证了设备之间电源的依赖性;直至最后进入省电模式,等待用户或者RTC唤醒;在唤醒过程中则会按照设备注册的顺序依次恢复每个设备的电源进入正常工作状态,解冻相关的进程,然后再进行浅度休眠的唤醒流程。
1、深度休眠入口
根据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; // suspend_test 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()开始挂起设备。
2、冻结进程
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; // 禁止usermodehelper error = usermodehelper_disable(); if (error) goto Finish; // 冻结所有可以冻结的进程 error = suspend_freeze_processes(); if (!error) return 0; // 解冻所有进程 suspend_thaw_processes(); // 使能usermodehelper usermodehelper_enable(); Finish: // 通知休眠结束 pm_notifier_call_chain(PM_POST_SUSPEND); pm_restore_console(); return error;}
这里有一个notifier机制后面要专门分析下。
3、挂起设备
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中
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函数。
// 函数将会调用所有的非sysdev设备的prepare()接口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)) { /* Wake-up requested during system sleep transition. */ pm_runtime_put_noidle(dev); error = -EBUSY; } else { // 执行prepare()函数 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链表中 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)) { // 逆序遍历链表,即先suspend后注册的设备,符合设备与父设备电源挂起的先后原则 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) { // 类的suspend优先 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) { // device_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) { // 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。
4、挂起处理器
static int suspend_enter(suspend_state_t state){ int error; // 处理器的休眠准备函数 if (suspend_ops->prepare) { error = suspend_ops->prepare(); if (error) return error; } // 执行非sysdev的late suspend函数 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; // 关闭非启动cpu error = disable_nonboot_cpus(); if (error || suspend_test(TEST_CPUS)) goto Enable_cpus; // 挂起中断 arch_suspend_disable_irqs(); BUG_ON(!irqs_disabled()); // 挂起sysdev error = sysdev_suspend(PMSG_SUSPEND); if (!error) { if (!suspend_test(TEST_CORE)) // 处理器的休眠进入函数,休眠流程运行至此 error = suspend_ops->enter(state); // 唤醒sysdev sysdev_resume(); } // 使能中断 arch_suspend_enable_irqs(); BUG_ON(irqs_disabled()); Enable_cpus: // 使能非启动cpu enable_nonboot_cpus(); Platform_wake: // 处理器开始唤醒 if (suspend_ops->wake) suspend_ops->wake(); Power_up_devices: // 执行非sysdev的early resume函数 dpm_resume_noirq(PMSG_RESUME); Platfrom_finish: // 处理器休眠结束 if (suspend_ops->finish) suspend_ops->finish(); return error;}
在这个阶段首先看处理器是否需要做一些准备,接下来执行非sysdev的late suspend函数,然后处理器做休眠前最后的准备、关闭非启动cpu、挂起中断,再挂起sysdev,最后进入处理器的挂起函数,至此休眠流程结束,处理器等待用户或者RTC唤醒。
附1、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) { // 执行所有设备的late suspend函数 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);
附2、中断关闭流程
在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) { // 如果中断没有被激活或者中断的IRQF_TIMER标志被置位则不关闭中断 // 在以后的内核版本中这个标志位被换成了IRQF_NO_SUSPEND // 新版的IRQF_TIMER = (__IRQF_TIMER | IRQF_NO_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、按键等;如果是其他中断则将打开的中断关闭掉。
附3、dpm_list链表
dpm_list是内核中用于设备电源管理的链表,设备注册时通过一系列的调用 device_register() -> device_add() -> device_pm_add() 最后在device_pm_add()中将设备加入dpm_list链表中:
// 设备创建时都会调用的函数,将设备加入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) { /* * We refuse to register parentless devices while a PM * transition is in progress in order to avoid leaving them * unhandled down the road */ 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链表中移除:
// 设备注销时都会调用的函数,将设备从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);}