购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

4.1 Linux PM Core的设计与实现

在本节,我们主要对Linux内核中PM Core的实现机制进行分析,包括架构设计概览、模块功能详解、配置信息解析、主要数据结构、主要函数实现以及软件处理流程等。

4.1.1 架构设计概览

PM Core在Linux内核低功耗软件栈中的位置如图4-1所示。

4.1.2 模块功能详解

PM Core模块是suspend/resume(睡眠/唤醒)主流程,它把Linux内核中各个与低功耗睡眠相关的模块组合在一起,包括但不限于图4-1中所示的PM notifier、DPM、syscore等模块,以便系统在满足睡眠条件时能按照设定的流程进入睡眠状态,在唤醒事件到来时又能按照设定的流程唤醒到睡眠之前的状态,保证系统的无障碍运行。

图4-1 PM Core在低功耗软件栈中的位置

4.1.3 配置信息解析

1)PM Core功能主要受以下宏控制:CONFIG_PM、CONFIG_SUSPEND、CONFIG_PM_AUTOSLEEP、CONFIG_FREEZER、CONFIG_PM_SLEEP。

2)相关代码主要在kernel/power/目录下。

kernel/power/main.c:提供用户态接口以及PM notif ier相关接口。

kernel/power/suspend.c:睡眠唤醒功能的主流程。

kernel/power/console.c:睡眠唤醒过程中对console的处理逻辑。

kernel/power/process.c:睡眠唤醒过程中对进程的处理逻辑。

关于目录下的其他文件,我们会在本书中其他章展开讲述。

4.1.4 主要数据结构

1.suspend_state_t

suspend_state_t定义在include/linux/suspend.h文件中,表示低功耗要进入的状态。在内核中主要有4种状态,如下:

1)PM_SUSPEND_ON:设备处于全电源状态,也就是正常工作状态。

2)PM_SUSPEND_TO_IDLE:外设做完suspend回调后,进入idle回调,不会去使能从核。

3)PM_SUSPEND_STANDBY:设备处于省电状态,但能够接收某些事件,具体的行为取决于具体的设备。

4)PM_SUSPEND_MEM:挂起到内存(suspend to memory),设备进入睡眠状态,但全部数据还保存在内存中,仅仅有某些外部中断才能够唤醒设备。

5)PM_SUSPEND_MAX:表示几种suspend状态的最大值,函数try_to_suspend会将autosleep_state值与其作比较,若autosleep_state大于或者等于该值,则调用hibernate()函数。

6)大多数Android设备仅支持PM_SUSPEND_ON和PM_SUSPEND_MEM,所以后续讨论中说到suspend状态时均是指PM_SUSPEND_MEM。

2.suspend_stats结构体

该结构体定义在include/linux/suspend.h中,主要记录低功耗流程中的维测数据。

在定义中,success表示执行成功的次数,fail表示执行失败的次数,其他字段的含义就如变量字面意思,这里不再一一说明。

3.platform_suspend_ops结构体

该结构体定义在include/linux/suspend.h中,主要记录与平台相关的低功耗主流程相关的回调函数。

1)valid回调:回调以确定平台是否支持给定的系统睡眠状态。有效(即支持的)状态在/sys/power/state中公布,即前文所描述的suspend_state_t。注意,如果条件不正确,仍然可能无法进入给定的系统睡眠状态。

2)begin回调:初始化到给定系统睡眠状态的转换,在挂起设备之前执行。@begin传递给平台代码的信息应在@end执行后立刻被忽略掉。如果@begin失败(即返回非零),PM主流程将不会调用@prepare()、@enter()和@finish()回调。begin回调是可选的,不是一定要实现的。但是一旦实现了,那么传递给@enter()的参数就是多余的,应忽略。

3)prepare回调:准备平台以进入由@begin()指示的系统睡眠状态。@begin()在设备挂起之后(即为每个设备执行了适当的.suspend()方法)和设备驱动程序的suspend_late回调之前调用。成功时返回0,否则返回负的错误代码,在这种情况下,系统无法进入所需的睡眠状态。此时不会调用@prepare_late()、@enter()和@wake()。

4)prepare_late回调:完成平台准备,以进入@begin()指示的系统睡眠状态。@prepare_late在禁用nonboot CPU之前和执行设备驱动程序的suspend_late回调之后调用。成功时返回0,否则返回负的错误代码,在这种情况下,系统无法进入所需的睡眠状态(@enter()将不会执行)。

5)enter回调:进入由@begin()指示的系统睡眠状态,如果未实现@begin(),则进入由参数表示的系统睡眠状态。此回调是必填的。成功时返回0,否则返回一个为负的错误码,在这种情况下,系统无法进入所需的睡眠状态。

6)wake回调:当系统刚刚离开睡眠状态时,在启用nonboot CPU之后且执行设备驱动程序的resume_early回调之前调用。此回调是可选的,但应由实现@prepare_late()的平台实现。如果实现,则它始终在@prepare_late和@enter()之后调用,即使其中一个失败。

7)finish回调:完成平台的唤醒流程。@finish紧跟在调用设备驱动程序的resume_early回调之后调用(详见源码suspend.c中的函数suspend_enter)。此回调是可选的,但如果这个平台实现了prepare回调,那么也应该实现finish回调。如果实现了,则它始终在@enter()和@wake()之后调用,即使其中一个失败。

8)suspend_again回调:返回系统是否应再次挂起。如果平台希望在挂起期间轮询传感器或执行某些代码,而不调用用户空间和大多数设备,则使用该回调。suspend_again回调是一个假设已经设置了定期唤醒或警报唤醒的绝佳设计。

9)end回调:在恢复设备后立即由PM主流程调用,表示系统已返回工作状态,或向睡眠状态的转换已中止。此回调是可选的,但应由实现@begin()的平台实现。因此,实现@begin()的平台还应提供@end(),该@end()回调函数负责清理在@enter()之前中止的转换。

10)recover回调:从挂起失败中恢复平台。如果设备挂起失败,由PM主流程调用。此回调是可选的,仅应由在这种情况下需要特殊恢复操作的平台实现。

4.1.5 主要函数实现

1.pm_suspend

该函数是PM Core睡眠流程的主入口函数,供autosleep中的suspend_work执行时调用。函数首先会判断入参状态是否为有效状态,然后会进入下一层函数enter_state中执行。该函数同时会记录睡眠失败和成功的相关维测记录,以备在定位问题时查看。

函数原型如下:

入参state表示suspend流程要进入的状态。返回0表示执行成功,返回其他值表示执行失败。

pm_suspend的具体实现代码如下:

2.enter_state

该函数是PM Core睡眠流程函数,由函数pm_suspend调用。

函数原型如下:

入参state表示suspend流程要进入的状态。返回0表示执行成功,返回其他值表示执行失败。

enter_state的具体实现代码如下:

主要处理过程如下。

1)enter_state会调用valid_state来判断当前suspend_ops是否有赋值来满足睡眠流程调用;

2)suspend_ops是通过各芯片平台调用suspend_set_ops来定制化赋值的,每个平台都有自己的实现,通过suspend_set_ops来赋值给suspend_ops以供PM Core调用。

3)接下来调用suspend_prepare来执行PM notifier回调,调用suspend_freeze_processes来冻结可以冻结的进程。(notif ier与进程冻结的相关实现会在后文中单独介绍。)

当以上3个处理过程都成功后,会进入下一级函数suspend_devices_and_enter中执行更进一步的睡眠处理流程。

3.suspend_devices_and_enter

该函数是PM Core睡眠流程函数,由函数enter_state调用。

函数原型如下:

入参state表示suspend流程要进入的状态。返回0表示执行成功,返回其他值表示执行失败。

suspend_devices_and_enter的具体实现代码如下:

主要处理过程如下。

1)通过调用platform_suspend_begin来执行平台注册的suspend_ops的begin回调;

2)通过调用dpm_suspend_start来执行DPM的prepare与suspend两个级别的回调处理。(DPM的内容会在后文中详细讲解。)

3)通过suspend_enter来进入下一层级的SR流程。

4)需要注意的是,睡眠流程是对称的,前面是睡眠流程,睡眠流程退出后接着执行唤醒流程,从suspend_enter睡下去,从suspend_enter醒过来并退出。

5)从suspend_enter退出表示睡眠结束,系统会接着调用dpm_resume_end来执行DPM中与prepare、suspend对应的resume、complete回调。

6)然后通过platform_resume_end调用平台注册的与suspend_ops的begin回调对应的end回调。

4.suspend_enter

该函数是PM Core睡眠流程函数,由函数suspend_devices_and_enter调用。

函数原型如下:

入参state表示suspend流程要进入的状态,出参*wakeup表示在suspend_enter中调用pm_wakeup_pending的返回值,即是否有电源状态转换挂起请求终止suspend。返回0表示执行成功,返回其他值表示执行失败。

suspend_enter的具体实现代码如下:

函数主要功能如下。

1)通过调用platform_suspend_prepare来执行平台注册的suspend_ops的prepare回调。

2)通过调用DPM接口dpm_suspend_late来执行DPM中suspend_late级别的回调。

3)通过调用DPM接口dpm_suspend_noirq来执行DPM中suspend_noirq级别的回调。

4)通过调用platform_suspend_prepare_noirq来执行平台注册的suspend_ops的prepare_late回调。

5)通过调用suspend_disable_secondary_cpus来停掉未启动的CPU;

6)通过arch_suspend_disable_irqs来屏蔽中断,这样之后的流程中就不会再响应中断了。

7)通过调用syscore_suspend来回调系统中注册的syscore_suspend回调函数,该级别的回调是在屏蔽中断的上下文中执行的。

8)通过调用suspend_ops的enter回调来执行平台注册的最终的平台相关的suspend函数,内核自身的PM Core走到这里基本上就停止了。

9)需要注意的是,PM Core从suspend_ops的enter回调睡下去,也需要从suspend_ops的enter回调中醒过来。

10)PM Core醒来后会按照睡眠流程对称的resume流程来执行唤醒动作:syscore_resume→arch_suspend_enable_irqs→suspend_enable_secondary_cpus→platform_resume_noirq→dpm_resume_noirq→platform_resume_early→dpm_resume_early→platform_resume_finish。

注意

在一个系统中,如果要使能PM Core,除了4.1.3节所说的需要打开宏之外,还需要调用suspend_set_ops来注册suspend_ops的enter回调函数,这个suspend_set_ops可以由各平台自己实现,也可以使用内核的PSCI框架,如图4-2所示。(PSCI的相关内容会在后文中详细讲解。)

图4-2 PSCI注册suspend_set_ops示意图

4.1.6 软件处理流程

前面几节对PM Core主流程的主要功能及主要结构体做了分析和讲解,接下来我们分析下具体处理流程,包括系统是如何进入睡眠的,又是如何被唤醒的。

1.睡眠流程

如果要打开内核中的PM特性开关,则需要按照如下流程执行。(睡眠流程执行时序如图4-3所示。)

图4-3 睡眠流程执行时序

1)在init.rc中,通过echo mem>/sys/power/autosleep触发睡眠持锁检查。

2)如果检查到没有组件持锁,则继续进入PM Core睡眠流程。如果有组件持锁,则停止进入睡眠流程。

3)在PM Core睡眠流程中,系统会挂起外设、去使能从核、回调syscore回调函数、挂起CPU、在ATF中执行安全相关操作,最后给主控核请求下电。

4)主控核给内核子系统下电后,需要接管内核子系统的唤醒中断操作,以便在需要时把内核系统唤醒。

需要说明的是,在执行过程中可以阻止睡眠,一旦某个环节返回错误,则停止本次睡眠流程并返回。

当前实际的执行过程要比我们说的复杂一些,这里只是为了帮助大家理解,所以对过程没有细化,在后文中我们会逐个对子系统、子框架展开更详细的讲解。

睡眠流程的最后阶段主要执行以下动作,如图4-4所示。

2.唤醒流程

低功耗主控核接收到内核子系统的唤醒中断后,会对其进行上电解复位动作,后续则执行睡眠流程的逆流程,在此不做过多介绍,后面会对全流程的每个组成部分进行拆解介绍。

图4-4 睡眠流程最后阶段执行的动作 1f1GKt8cVbtN5iSdP0UMUzjrdrTyEhsTUzj1iW8VPezDJQWQb5porlHzPBOhTNa6

点击中间区域
呼出菜单
上一章
目录
下一章
×