- static int suspend_enter(suspend_state_t state, bool *wakeup)
- {
- int error;
-
- if (need_suspend_ops(state) && suspend_ops->prepare) {
- error = suspend_ops->prepare();
- if (error)
- goto Platform_finish;
- }
-
- error = dpm_suspend_end(PMSG_SUSPEND);
- if (error) {
- printk(KERN_ERR "PM: Some devices failed to power down\n");
- goto Platform_finish;
- }
-
- if (need_suspend_ops(state) && suspend_ops->prepare_late) {
- error = suspend_ops->prepare_late();
- if (error)
- goto Platform_wake;
- }
-
- if (suspend_test(TEST_PLATFORM))
- goto Platform_wake;
-
- /*
- * PM_SUSPEND_FREEZE equals
- * frozen processes + suspended devices + idle processors.
- * Thus we should invoke freeze_enter() soon after
- * all the devices are suspended.
- */
- if (state == PM_SUSPEND_FREEZE) {
- freeze_enter();
- 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 = 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();
- }
-
- arch_suspend_enable_irqs();
- BUG_ON(irqs_disabled());
-
- Enable_cpus:
- enable_nonboot_cpus();
-
- Platform_wake:
- if (need_suspend_ops(state) && suspend_ops->wake)
- suspend_ops->wake();
-
- dpm_resume_start(PMSG_RESUME);
-
- Platform_finish:
- if (need_suspend_ops(state) && suspend_ops->finish)
- suspend_ops->finish();
-
- return error;
- }
f1)该接口处理完后,会通过返回值告知是否enter成功,同时通过wakeup指针,告知调用者,是否有wakeup事件发生,导致电源状态切换失败。 f2)调用suspend_ops的prepare回调(有的话),通知平台代码,以便让其在即将进行状态切换之时,再做一些处理(需要的话)。该回调可能失败(平台代码出现意外),失败的话,需要跳至Platform_finish处,调用suspend_ops的finish回调,执行恢复操作。
f3)调用dpm_suspend_end,调用所有设备的->suspend_late和->suspend_noirq回调函数(具体可参考“
Linux电源管理(4)_Power Management Interface”的描述),suspend late suspend设备和需要在关中断下suspend的设备。需要说明的是,这里的noirq,是通过禁止所有的中断线的形式,而不是通过关全局中断的方式。同样,该操作可能会失败,失败的话,跳至Platform_finish处,执行恢复动作。
f4)调用suspend_ops的prepare_late回调(有的话),通知平台代码,以便让其在最后关头,再做一些处理(需要的话)。该回调可能失败(平台代码出现意外),失败的话,需要跳至Platform_wake处,调用suspend_ops的wake回调,执行device的resume、调用suspend_ops的finish回调,执行恢复操作。
f5)如果是suspend to freeze,执行相应的操作,包括冻结进程、suspended devices(参数为PM_SUSPEND_FREEZE)、cpu进入idle。如果有任何事件使CPU从idle状态退出,跳至Platform_wake处,执行wake操作。
f6)调用disable_nonboot_cpus,禁止所有的非boot cpu。也会失败,执行恢复操作即可。
f7)调用arch_suspend_disable_irqs,关全局中断。如果无法关闭,则为bug。
f8)调用syscore_suspend,suspend system core。同样会失败,执行恢复操作即可。有关syscore,我会在另一篇**中详细描述。
f9)如果很幸运,以上操作都成功了,那么,切换吧。不过,别高兴太早,还得调用pm_wakeup_pending检查一下,这段时间内,是否有唤醒事件发生,如果有就要终止suspend。
f10)如果一切顺利,调用suspend_ops的enter回调,进行状态切换。这时,系统应该已经suspend了……
f11)suspend过程中,唤醒事件发生,系统唤醒,该函数接着执行resume动作,并最终返回。resume动作基本上是suspend的**作,就不再继续分析了。
f12)或者,由于意外,suspend终止,该函数也会返回。
g)suspend_enter返回,如果返回原因不是发生错误,且不是wakeup事件。则调用suspend_ops的suspend_again回调,检查是否需要再次suspend。再什么情况下要再次suspend呢?需要看具体的平台了,谁知道呢。
h)继续resume操作,resume device、start ftrace、resume console、suspend_ops->end等等。
i)该函数返回后,表示系统已经resume。
4.5 suspend_finish
比较简单:
- static void suspend_finish(void)
- {
- suspend_thaw_processes();
- pm_notifier_call_chain(PM_POST_SUSPEND);
- pm_restore_console();
- }
a)恢复所有的用户空间进程和内核线程。
b)发送suspend结束的通知。
c)将console切换回原来的。
5. 重要知识点回顾
5.1 VT switch 通常情况下,系统控制台模块(drivers\tty\vt\)会在suspend的过程中,重新分配一个console,并将控制台切换到该console上。然后在resume时,切换回旧的console。这就是VT switch功能。VT switch是很耗时的,因此内核提供了一些机制,控制是否使用这个功能:
1)提供一个接口函数pm_set_vt_switch(drivers\tty\vt\vt_ioctl.c),方便其它内核模块从整体上关闭或者开启VT switch功能。
2)VT switch全局开关处于开启状态时,满足如下的一种条件(可参考kernel\power\console.c相关的描述),即会使能VT switch
a)有console driver调用pm_vt_switch_required接口,显式的要求使能VT switch。PM core的console模块会把这些信息记录在一个名称为pm_vt_switch_list的链表中。
b)系统禁止在suspend的过程中suspend console(由kernel/printk.c中的console_suspend_enabled变量控制)。很有可能需要使用console查看suspend过程,此时为了使console不混乱,有必要进行VT switch。
c)没有任何console driver关心是否需要VT switch,换句话说没有任何driver调用pm_vt_switch_required接口要求使能或禁止VT switch功能。此时会按照旧的习惯,进行VT switch。
因此,suspend过程对console的处理分为4步:
prepare console:负责在需要VT swich时,将当前console切换到SUSPEND console。
- int pm_prepare_console(void)
- {
- if (!pm_vt_switch())
- return 0;
-
- orig_fgconsole = vt_move_to_console(SUSPEND_CONSOLE, 1);
- if (orig_fgconsole < 0)
- return 1;
-
- orig_kmsg = vt_kmsg_redirect(SUSPEND_CONSOLE);
- return 0;
- }
suspend console:挂起console,由kernel/printk.c实现,主要是hold住console用的互斥锁,使他人无法使用console。
resume console:对console解锁。
restore console:将console恢复为初始的console。
- void pm_restore_console(void)
- {
- if (!pm_vt_switch())
- return;
-
- if (orig_fgconsole >= 0) {
- vt_move_to_console(orig_fgconsole, 0);
- vt_kmsg_redirect(orig_kmsg);
- }
- }
也许,您会问,why VT switch?先留着这个疑问吧,等到分析控制台时再回答。
5.2 freezing of task 进程的freezing功能,是suspend、hibernate等电源管理功能的组成部分,在新版本内核中,它被独立出来,作为一个独立的电源管理状态(freeze)。该功能的目的,是在电源管理的状态切换过程中,确保所有用户空间进程和部分内核线程处于一个稳定的状态。有关该功能的具体描述,请参考wowotech后续的**。
5.3 PM notifier PM notifier是基于内核blocking notifier功能实现的。blocking notifier提供了一种kernel内部的消息通知机制,消息接受者通过notifier注册的方式,注册一个回调函数,关注消息发送者发出的notifier。当消息产生时,消息产生者通过调用回调函数的形式,通知消息接受者。这种调用,是可以被阻塞的,因此称作blocking notifier。
那suspend功能为什么使用notifier呢?原因可能有多种,这里我举一个例子,这是我们日常开发中可能会遇到的。
由之前的描述可知,suspend过程中,suspend device发生在进程被freeze之后,resume device发生在进程被恢复之前。那么:
1)如果有些设备就需要在freeze进程之前suspend怎么办?
2)如果有些设备的resume动作需要较多延时,或者要等待什么事情发生,那么如果它的resume动作发生在进程恢复之前,岂不是要阻止所有进程的恢复?更甚者,如果该设备要等待某个进程的数据才能resume,怎么办?
再来看suspend_prepare和suspend_finish中的处理:
- static int suspend_prepare(suspend_state_t state) {
- …
- error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
- if (error)
- goto Finish;
-
- error = suspend_freeze_processes();
- …
- }
-
- static void suspend_finish(void)
- {
- suspend_thaw_processes();
- pm_notifier_call_chain(PM_POST_SUSPEND);
- pm_restore_console();
- }
原来PM notifier是在设备模型的框架外,开了一个后门,那些比较特殊的driver,可以绕过设备模型,直接接收PM发送的suspend信息,以便执行自身的suspend动作。特别是resume时,可以在其它进程都正好工作的时候,只让suspend进程等待driver的resume。
感兴趣的读者,可以围观一下下面这个活生生的例子(顺便提一下,好的设计是不应该有例外的):
drivers\video\omap2\dss\core.c
5.4 device PM ops 和platform PM ops的调用时机 对Linux驱动工程师来说,device PM ops和platform PM ops就是电源管理(suspend)的全部,只要在合适的地方,实现合适的回调函数,即可实现系统的电源管理。但现实太复杂了,以至于kernel提供的这两个数据结构也很复杂,再回忆一下,如下:
- struct dev_pm_ops {
- int (*prepare)(struct device *dev);
- void (*complete)(struct device *dev);
- int (*suspend)(struct device *dev);
- int (*resume)(struct device *dev);
- int (*freeze)(struct device *dev);
- int (*thaw)(struct device *dev);
- int (*poweroff)(struct device *dev);
- int (*restore)(struct device *dev);
- int (*suspend_late)(struct device *dev);
- int (*resume_early)(struct device *dev);
- int (*freeze_late)(struct device *dev);
- int (*thaw_early)(struct device *dev);
- int (*poweroff_late)(struct device *dev);
- int (*restore_early)(struct device *dev);
- int (*suspend_noirq)(struct device *dev);
- int (*resume_noirq)(struct device *dev);
- int (*freeze_noirq)(struct device *dev);
- int (*thaw_noirq)(struct device *dev);
- int (*poweroff_noirq)(struct device *dev);
- int (*restore_noirq)(struct device *dev);
- int (*runtime_suspend)(struct device *dev);
- int (*runtime_resume)(struct device *dev);
- int (*runtime_idle)(struct device *dev);
- };
-
- struct platform_suspend_ops {
- int (*valid)(suspend_state_t state);
- int (*begin)(suspend_state_t state);
- int (*prepare)(void);
- int (*prepare_late)(void);
- int (*enter)(suspend_state_t state);
- void (*wake)(void);
- void (*finish)(void);
- bool (*suspend_again)(void);
- void (*end)(void);
- void (*recover)(void);
- };
虽然内核的注释已经相当详细了,但我们一定会犯晕,到底该实现哪些回调?这些回调的应用场景又是什么?蜗蜗以为,要熟练使用这些回调,唯一的方法就是多coding、多理解。除此之外,我们可以总结一下在电源状态切换时,这些回调的调用时机,从侧面帮助理解。如下(只介绍和suspend功能有关的,struct dev_pm_ops简称D,struct platform_suspend_ops简称P):
5.5 suspend过程的同步和PM wakeup 最重要的事情,如果suspend的过程中,有唤醒事件产生怎么办?正常的流程,应该终止suspend,返回并处理事件。但由于suspend过程的特殊性,进程被freeze、关中断等等,导致事情并没有那么简单,以至于在很久的一段时间内,kernel都不能很好的处理。这也称作suspend过程的同步问题。
在美好的旧时光里,suspend大多用于热关机,因此同步问题的影响并不突出(因为操作并不频繁)。但来到新时代之后,事情变了,Android竟然用suspend作日常的待机(操作就相当频繁了),这时问题就大了。那怎么解决呢?得靠system wakeup framework,也就是suspend过程中所调用的pm_wakeup_pending接口所在的模块。我会在下一篇**中继续该模块的分析,这里就不再继续了。
@icecut @海中水 @yyy71cj
这篇对内核低功耗部分说得很详细