Linux电源管理(6)_Generic
PM之Suspend功能一文中的下图。
本文主要分析平台相关的CPU睡眠和唤醒,即下电和上电流程,以及ARM底层汇编代码实现。
内核版本:3.1.0 CPU:ARM Cortex-A7
1 平台相关函数执行流程
上图最后调入suspend_ops->enter,这是个平台相关的函数。
平台相关的cpu
suspend_enter函数:
如果有中断挂起,则直接返回;
读取设备的idle状态;
设置CPU0的热跳转寄存器,睡眠唤醒后,跳转到这个地址
允许cpu0睡眠
不屏蔽SCU断电、自断电功能
不屏蔽CPU自断电功能,当CPU进入WFI状态,CPU自动断电
改变CPU的频率、电压输出
调用cpu_suspend函数
恢复CPU的频率、电压输出
屏蔽自断电功能
屏蔽SCU断电、自断电功能
不允许CPU0睡眠
cpu_suspend函数调用流程图
2 睡眠过程详细分析
cpu_suspend:(arch/arm/kernel/suspend.c)
int
cpu_suspend(unsigned long arg, int (*fn)(unsigned
long))
函数携带两个参数,第二个参数是函数指针(参数是unsigned
long型的,返回值是int型的),第一个参数就是前面的函数指针被调用时需要的参数,故为unsigned
long型的
if
(!idmap_pgd) //非常有意思的一个变量,稍后再解释
return -EINVAL;
调用 __cpu_suspend
__cpu_suspend:(arch/arm/kernel/sleep.S)
携带的参数就是调用cpu_suspend函数时传入的参数。
R0(unsigned long
arg,实际上是个地址,地址存放的类型是suspend_args,是个参数,供R1函数调用时使用)
R1(睡眠函数,执行时需要的参数就是R0)
#define
cpu_suspend_size __glue(CPU_NAME,_suspend_size)
arch/arm/include/asm/glue-proc.h
#define
__glue(name,fn) ____glue(name,fn) arch/arm/include/asm/glue.h
#define
____glue(name,fn) name##fn
define CPU_NAME
cpu_v7
.equ cpu_v7_suspend_size, 4 * 8(arch/arm/mm/proc.v7.S)
故 cpu_suspend_size
== cpu_v7_suspend_size
入栈R4-R11,LR
没有定义MULTI_CPU,R4赋值cpu_suspend_size,为32
R5就是入栈后堆栈的地址,图中的1处
R4加上12
然后将堆栈地址减去(32+12),就是让开11个寄存器的值,图中的3处
入栈R0,R1
R0赋值堆栈地址加上8,图中的3处
R1赋值R4,就是44
R2赋值R5,就是图中的1处
R3赋值临时栈地址,文件下面定义了数个(看内核配置了多少个CPU来定)unsigned
long 型的地址空间
定义了多核,根据CPU的ID,获取当前CPU的临时栈地址
跳转到__cpu_suspend_save
__cpu_suspend_save:(arch/arm/kernel/suspend.c)
R3即当前CPU的临时栈地址,存入R0的值(物理的地址),图中的3处
以R0为基地址,入栈idmap_pgd的物理地址、入栈当前的堆栈(图中的1处,是个虚拟地址)、入栈唤醒函数(
cpu_do_resume 的物理地址)
cpu_do_suspend(glue-proc.h),-> cpu_v7_do_suspend
(arch/arm/mm/proc.v7.S)携带的参数R0就是刚入完3个寄存器后的堆栈地址
入栈R4-R10,LR;
以传入的参数R0为栈基地址,入栈R4、R5(PID、线程ID)
入栈R6-R11(域ID,页表基地址寄存器1、页表控制寄存器、系统控制寄存器、辅助寄存器、协处理器访问控制寄存器),加上上面的2个正好是8个;
弹出R4-R10,PC;
刷新cache、二级cache,保证数据确实写到了内存,栈空间也是内存的一部分
LR赋值cpu_suspend_abort
弹出r0,PC
,就是跳转到上面的fn函数指针处
若这个fn函数执行过程中返回了,则调转到lr处,就是cpu_suspend_abort
cpu_suspend_abort:
此时的SP就是图中的3处,弹出到R1,R2,R3。
判断R0的值,若不是0,则将其赋值1
堆栈SP赋值R2的值,图中的1处
堆栈弹出R4-R11,PC。实际是返回到cpu_suspend函数中,调用__cpu_suspend函数的地方。
fn函数执行过程,若最终执行成功,则执行wfi指令,CPU顺利下电
清除Icache,刷dcache。
关闭SMP位
关闭对应的CCI端口
然后进入WFI睡眠,低功耗状态
如果中间出现了差错,则直接返回1后,接着下面的cpu_suspend_abort执行,仍然能够返回到cpu_suspend函数,其中__cpu_suspend函数的返回值强行变为了1
3 唤醒过程详细分析
低功耗模式被唤醒后,跳转到唤醒地址
设置SVC模式,关闭IRQ、FIQ
使能对应的CCI端口
清除SMP位,清除跳转预测等,开启I
cache
跳转到cpu_resume的物理地址
cpu_resume:
获取当前CPU的临时栈地址,保存到R0中,图中的3处
设置SVC模式,关闭I、F
以R0为基地址,弹出R1、SP、PC,R1就是 idmap_pgd
跳转到cpu_do_resume,就是cpu_v7_do_resume
cpu_v7_do_resume
清除TLB、I cache、上下文ID<