Power Suspend&Resume
@liaoye@2021.5.10
power suspend 流程图............................................................................. 1
suspend流程关键点................................................................................. 6
suspend核心:initAutosuspend.......................................................... 7
读取wakeup_count值.................................................................... 8
mSuspendCounter判断................................................................... 9
回写wakeup_count值.................................................................. 10
mem 写入到 state........................................................................ 11
加入LOG分析流程...................................................................... 11
Resume流程....................................................................................... 13
wakeup events framework.................................................................. 14
wakelock和wakeup event framework关系................................. 15
如下图所示,suspend 主要流程:
1、 PowerManagerService.java 灭屏触发Autosuspend 流程
2、SystemSuspend.cpp检测上层/kernel wakelock是否都是释放掉,写入mem到/sys/power/state
3、suspend.c 冻结进程、设备suspend、关闭非启动CPU、关闭中断、syscore休眠、进入WFI、进入芯片相关的低功耗模式(SPM管理、关闭26M CLK 使用32K CLK)。
initAutosuspend函数是suspend流程的核心,是判断需不需要启动suspend流程的起始点,一旦灭屏后就会跑到initAutosuspend函数来。
@system/hardware/interfaces/suspend/1.0/default/SystemSuspend.cpp
initAutosuspend函数只有在第一次灭屏时候执行一次,起了一个线程,这个线程会一直循环读写wakeup_count判断系统中wakelock状态是否符合进入suspend的条件,主要三个工作:读取wakeup_count值、mSuspendCounter判断、回写wakeup_count值和mem 写入到 state。
读取wakeup_count值是已阻塞的方式,只有当前 wakeup events in progress为0(也就是当前没有wakeup event处理)时才会返回。如果有读取成功,判断返回值是否正常,决定是否执行下一步。否则说明有正在处理的wakeup events,则会继续重新读取。
具体来看下/sys/power/wakeup_count 读操作在kernel中的实现,代码如下:
@kernel/power/main.c
@driver/base/power/wakeup.c
pm_get_wakeup_count()函数有两个参数,一个是保存返回的count值得指针,另一个指示是否block,具体请参考代码逻辑:
a)如果block为false,直接读取registered wakeup events和wakeup events in progress两个counter值,将registered wakeup events交给第一个参数,并返回wakeup events in progress的状态(若返回false,说明当前有wakeup events正在处理,不适合suspend)。
b)如果block为true,定义一个等待队列,等待wakeup events in progress为0,再返回counter。
***在这里block为true,所以如果有正在处理的wakeup events,read进程会阻塞。其它模块(如auto sleep)发起的read,则可能不需要阻塞。
代码:mCounterCondVar.wait(counterLock, [this] { return mSuspendCounter == 0; });
mSuspendCounter 是用来记录当前系统中持有多少个上层wakelock,这里代码定义了一个条件等待锁,只有当 mSuspendCounter=0时才会执行下一步,否则会一直阻塞在这里。
将读到registered wakeup events count值回写wakeup_count节点,判断返回值是否成功,如果不成功(说明读、写的过程中产生了wakeup events),否则继续读、写,直到成功。
具体来看下/sys/power/wakeup_count 写操作在kernel中的实现,代码如下:
这里读取当前registered wakeup events和wakeup events in progress两个counter,并判断registered wakeup events counter是否和之前读取的一致,然后判断wakeup events in progress counter是否为0,如果是,则说明在这段时间中没有新的wakeup event产生,可以进入下一步;否则表示有新的wakeup event产生,会重新执行上述读、写步骤。
若上述流程都成功后,可以触发电源状态切换,也就是mem 写入到 state,正式进入suspend流程。
调用到kernel正式启动suspend流程,这部分代码逻辑上基本都比较简单,看上述流程图就行,不做详细分析。
为了更好的理解initAutosuspend的流程,在函数中添加了一些log:
如下是灭屏后打印出来的log信息:
initAutosuspend()只在第一次灭屏的时候调用一次,在enableAutoSuspend@
com_android_server_power_PowerManagerService.cpp中判断initAutosuspend 是否执行过,启动一个线程
05-11 17:10:19.430 592 598 I android.system.suspend@1.0-service: liaoye start enableAutosuspend: initialized = 0
05-11 17:10:19.430 592 598 I android.system.suspend@1.0-service: liaoye 0: start initAutosuspend
05-11 17:10:19.432 592 598 I android.system.suspend@1.0-service: liaoye 7: end initAutosuspend
05-11 17:10:19.432 592 598 I android.system.suspend@1.0-service: liaoye end enableAutosuspend: initialized = 1
读取 wakeup_count
05-11 17:10:19.532 592 6688 I android.system.suspend@1.0-service: liaoye 1: start read wakeup_count
wait lock 等待 mSuspendCounter = 0, 这里为1,所以会一直阻塞
05-11 17:10:29.373 592 6688 I android.system.suspend@1.0-service: liaoye 2: wait lock mSuspendCounter = 1
这里写入失败,重新上述步骤
05-11 17:11:04.095 592 6688 I android.system.suspend@1.0-service: liaoye 3: start write wakeup_count
上述步骤写入失败,重新读去wakeup_count
05-11 17:11:04.196 592 6688 I android.system.suspend@1.0-service: liaoye 1: start read wakeup_count
wait lock 等待 mSuspendCounter = 0, 这里为0,所以会进入下一步
05-11 17:11:04.196 592 6688 I android.system.suspend@1.0-service: liaoye 2: wait lock mSuspendCounter = 0
这里写入成功,继续下一步
05-11 17:11:04.196 592 6688 I android.system.suspend@1.0-service: liaoye 3: start write wakeup_count
写入mem到state,进入suspend 流程
05-11 17:11:04.196 592 6688 I android.system.suspend@1.0-service: liaoye 4: write mem to state
被唤醒或suspend失败
05-11 17:11:29.246 592 6688 I android.system.suspend@1.0-service: liaoye 5: wakeup
05-11 17:11:29.247 592 6688 I android.system.suspend@1.0-service: liaoye 6
又开始新一轮循环
05-11 17:11:29.349 592 6688 I android.system.suspend@1.0-service: liaoye 1: start read wakeup_count
resume流程基本和suspend流程是相反的,这里主要讲下从外部中断是如何产生的呢?又是如何唤醒系统的呢?
一般具有中断唤醒的设备会有一个interrupt pin硬件连接到SoC的gpio pin。一般来说,当设备需要唤醒系统的时候,会通过改变interrupt pin电平状态,而SoC会检测到这个变化,将SoC从睡眠中唤醒,唤醒走的与suspend相反的流程,当完成resume irq流程后会执行该设备注册中断服务程序,对该设备的中断事件做出对应的处理。
为了使能设备的唤醒能力,设备驱动中会在系统suspend的时候通过enable_irq_wake(irq)接口使能设备SoC引脚的中断唤醒能力,也就是对中断控制器的寄存器进行配置对应irq使能唤醒能力。
wakeup events framework 始终贯穿着suspend整个流程,这里了解下wakeup events framework 及在suspend流程中的作用。
1、wakeup events framework core,在drivers/base/power/wakeup.c中实现,提供了wakeup events framework的核心功能:
抽象wakeup source和wakeup event的概念;
向各个device driver提供wakeup source的注册、使能等接口;
向各个device driver提供wakeup event的上报、停止等接口;
向上层的PM core(包括wakeup count、auto sleep、suspend、hibernate等模块)提供wakeup event的查询接口,以判断是否可以suspend、是否需要终止正在进行的suspend。
2、wakeup events framework sysfs,将设备的wakeup信息,以sysfs的形式提供到用户空间,供用户空间程序查询、配置。在drivers/base/power/sysfs.c中实现。
3、wake lock/unlock,为了兼容Android旧的wakeup lock机制而留下的一个后门,扩展wakeup events framework的功能,允许用户空间程序报告/停止wakeup events。换句话说,该后门允许用户空间的任一程序决定系统是否可以休眠。
4、wakeup count,基于wakeup events framework,解决用户空间同步的问题。
5、auto sleep,允许系统在没有活动时(即一段时间内,没有产生wakeup event),自动休眠。
上部分引用:http://www.wowotech.net/linux_kenrel/wakeup_events_framework.html
简单来说wakeup event framework是wakelock的底层实现,wakelock提供接口给其他模块调用。其中wakelock包括两类:
上层wakelock:上层framework实现供上层的模块的使用,通过wake_lock和wake_unlock节点调用到kernel 实现的接口,最终都会调用到__pm_stay_awake()@wakeup.c
kernel wakelock:供kernel层的模块使用,最终都会调用到__pm_stay_awake()@wakeup.c
__pm_stay_awake()→ wakeup_source_report_event()→ wakeup_source_activate(), wakeup_source_activate()函数实现如下:
作用是对combined_event_count变量增加一次计数,combined_event_count是个比较重要的变量,作用是用来保存registered wakeup events和wakeup events in progress两个计数,initAutosuspend函数也是通过/sys/power/wake_count节点最终读取到combined_event_count来进行判断系统中wakeup event状态是否能进入suspend流程。
registered wakeup events:记录了系统运行以来产生的所有wakeup event的个数,wakeup events in progress处理完成wakeunlock时加1;
@wakeup_source_deactivate()
wakeup events in progress:记录正在处理的wakeup event个数,在wakelock时加1;
@wakeup_source_activate()
combined_event_count:是上述两个计数的组合,是一个32位的it,前16位用于registered wakeup events,后16位用于wakeup events in progress。
从上图可以看出,不管是上层还是底层申请wakelock,最终都会记录到combined_event_count
,而combined_event_count是作为是否启动suspend流程和判断suspend流程中是否产生新的wakeup event 需要退出suspend流程的重要条件。
eof