1.Linux 描述的电源状态
-
On(on) S0 - Working
-
Standby (standby) S1 - CPU and RAM are powered but not executed
-
Suspend to RAM(mem) S3 - RAM is powered and the running content is saved to RAM
-
Suspend to Disk,Hibernation(disk) S4 - All content is saved to Disk and power down
S3 aka STR(suspend to ram),挂起到内存,简称待机。计算机将目前的运行状态等数据存放在内存,关闭硬盘、外设等设备,进入等待状态。此时内存仍然需要电力维持其数据,但整机耗电很少。恢复时计算机从内存读出数据,回到挂起前的状态,恢复速度较快。对DDR的耗电情况进行优化是S3性能的关键,大多数手持设备都是用S3待机。
S4 aka STD(suspend to disk),挂起到硬盘,简称休眠。把运行状态等数据存放在硬盘上某个文件或者某个特定的区域,关闭硬盘、外设等设备,进入关机状态。此时计算机完全关闭,不耗电。恢复时计算机从休眠文件/分区中读出数据,回到休眠前的状态,恢复速度较慢。
二. 给u-boot添加suspend命令
u-boot全速运行,耗电83ma
u-boot suspend:耗电33ma ==>外设备没有完全关闭,比如声卡、网卡
进入休眠模式的方法:
/* 1. 配置GPIO: 比如想维持LED亮或灭, 用于唤醒CPU的引脚要设为中断功能 /
/ 2. 设置INTMSK屏蔽所有中断: 在sleep模式下,这些引脚只是用于唤醒系统,当CPU正常运行时可以重新设置INTMSK让这些引脚用于中断功能 /
/ 3. 配置唤醒源 /
/ 4. 设置MISCCR[13:12]=11b, 使得USB模块进入休眠 /
/ 5. 在GSTATUS[4:3]保存某值, 它们可以在系统被唤醒时使用 /
/ 6. 设置 MISCCR[1:0] 使能数据总线的上拉电阻 /
/ 7. 清除 LCDCON1.ENVID 以停止LCD /
/ 8. 读这2个寄存器: rREFRESH and rCLKCON, 以便填充TLB
* 如果不使用MMU的话,这个目的可以忽略
/
/ 9. 设置 REFRESH[22]=1b,让SDRAM进入self-refresh mode /
/ 10. 等待SDRAM成功进入self-refresh mode /
/ 11.设置 MISCCR[19:17]=111b以保护SDRAM信号(SCLK0,SCLK1 and SCKE) /
/ 12. 设置CLKCON的SLEEP位让系统进入sleep mode */
在进入str之前,1.先要填充TLB,将页表复制到TLB中,防止SDRAM进入自刷新模式,虚拟地址不能通过页表找到物理地址。
2.将SDRAM的部分指令到Icache中,以便在SDRAM自刷新到sleep期间cpu能够正常运行。3.让SDRAM进入self-refresh mode 4.设置CLKCON的SLEEP位让系统进入sleep mode
SDRAM的自刷新
下面图片对Linux suspend&resume过程做了一个概述,读者可以顺着这个流程阅读内核源代码。具体的说明,可以参考后面的代码分析。
在用户空间执行如下操作:
echo "freeze" > /sys/power/state
echo "standby" > /sys/power/state
echo "mem" > /sys/power/state
会通过sysfs触发suspend的执行,那么echo会底层的哪些动作呢?
先看看有一个
power_attr(state)的源码如下:
//位于\kernel\power\power.h
#define power_attr(_name) \
static struct kobj_attribute _name##_attr = { \
.attr = { \
.name = __stringify(_name), \
.mode = 0644, \
}, \
.show = _name##_show, \
.store = _name##_store, \
}
===========================
//将power_attr(state)代入,可知定义可一个结构体
static struct kobj_attribute state_attr = { \
.attr = { \
.name = __stringify(_name), \
.mode = 0644, \
}, \
.show = state_show, \
.store = state_store, \
}
接着发现在attr_group结构体中
static struct attribute * g[] = {
&state_attr.attr, //这了取到state_attr
#ifdef CONFIG_PM_TRACE
&pm_trace_attr.attr,
&pm_trace_dev_match_attr.attr,
#endif
#ifdef CONFIG_PM_SLEEP
&pm_async_attr.attr,
&wakeup_count_attr.attr,
#ifdef CONFIG_PM_AUTOSLEEP
&autosleep_attr.attr,
#endif
#ifdef CONFIG_PM_WAKELOCKS
&wake_lock_attr.attr,
&wake_unlock_attr.attr,
#endif
#ifdef CONFIG_PM_DEBUG
&pm_test_attr.attr,
#endif
#ifdef CONFIG_PM_SLEEP_DEBUG
&pm_print_times_attr.attr,
#endif
#endif
#ifdef CONFIG_FREEZER
&pm_freeze_timeout_attr.attr,
#endif
NULL,
};
static struct attribute_group attr_group = { //attr_group中可以拿到attribute
.attrs = g,
};
static int __init pm_init(void)
{
int error = pm_start_workqueue();
if (error)
return error;
hibernate_image_size_init();
hibernate_reserved_size_init();
power_kobj = kobject_create_and_add("power", NULL);
if (!power_kobj)
return -ENOMEM;
error = sysfs_create_group(power_kobj, &attr_group);
if (error)
return error;
pm_print_times_init();
return pm_autosleep_init();
}
这里在power目录下创建了一个state文件,对应的读函数是state_show,写函数是state_store,所以echo “mem” > /sys/power/state会触发state_store函数。
//位于kernel\power\main.c
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t n)
{
suspend_state_t state;
int error;
error = pm_autosleep_lock();
if (error)
return error;
if (pm_autosleep_state() > PM_SUSPEND_ON) {
error = -EBUSY;
goto out;
}
state = decode_state(buf, n);
if (state < PM_SUSPEND_MAX)
error = pm_suspend(state); 【见下解析】
else if (state == PM_SUSPEND_MAX)
error = hibernate();
else
error = -EINVAL;
out:
pm_autosleep_unlock();
return error ? error : n;
}
power_attr(state);
power_attr定义了一个名称为state的attribute文件,该文件的store接口为state_store,该接口在lock住autosleep功能后,解析用户传入的buffer(freeze、standby or mem),转换成state参数。
state参数的类型为suspend_state_t,在include\linux\suspend.h中定义,为电源管理状态在内核中的表示。具体如下:
typedef int __bitwise suspend_state_t;
#define PM_SUSPEND_ON ((__force suspend_state_t) 0)
#define PM_SUSPEND_FREEZE ((__force suspend_state_t) 1)
#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 2)
#define PM_SUSPEND_MEM ((__force suspend_state_t) 3)
#define PM_SUSPEND_MIN PM_SUSPEND_FREEZE
#define PM_SUSPEND_MAX ((__force suspend_state_t) 4)
下面看看pm_suspend函数和
int pm_suspend(suspend_state_t state)
{
int error;
if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
return -EINVAL;
error = enter_state(state);//【见下解析】
if (error) {
suspend_stats.fail++;
dpm_save_failed_errno(error);
} else {
suspend_stats.success++;
}
return error;
}
static int enter_state(suspend_state_t state)
{
int error;
if (!valid_state(state)) //【见下解析】
return -ENODEV;
if (!mutex_trylock(&pm_mutex))
return -EBUSY;
if (state == PM_SUSPEND_FREEZE)
freeze_begin();
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(state);//【见下解析】
if (error)
goto Unlock;
if (suspend_test(TEST_FREEZER))
goto Finish;
pr_debug("PM: Entering %s sleep\n", pm_states[state]);
pm_restrict_gfp_mask();
//【见下解析】
error = suspend_devices_and_enter(state);//让设备进入休眠状态
pm_restore_gfp_mask();
Finish:
pr_debug("PM: Finishing wakeup.\n");
suspend_finish();
Unlock:
mutex_unlock(&pm_mutex);
return error;
}
bool valid_state(suspend_state_t state)
{
if (state == PM_SUSPEND_FREEZE) {
return true;
}
/*
* PM_SUSPEND_STANDBY and PM_SUSPEND_MEMORY states need lowlevel
* support and need to be valid to the lowlevel
* implementation, no valid callback implies that none are valid.
*/
return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
}
//调用valid_state,判断该平台是否支持该电源状态。
//如果是freeze,无需平台代码参与即可支持,直接返回true。对于standby和mem,则需要调用suspend_ops的valid回掉,由底层平台代码判断是否支持。
static int suspend_prepare(suspend_state_t state)
{
int error;
if (!sleep_state_supported(state))
return -EPERM;
pm_prepare_console();
error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);//通知所有关心“休眠消息”的驱动程序
if (error)
goto Finish;
trace_suspend_resume(TPS("freeze_processes"), 0, true);
error = suspend_freeze_processes();//冻结APP和内核线程
trace_suspend_resume(TPS("freeze_processes"), 0, false);
if (!error)
return 0;
suspend_stats.failed_freeze++;
dpm_save_failed_step(SUSPEND_FREEZE);
Finish:
pm_notifier_call_chain(PM_POST_SUSPEND);
pm_restore_console();
return error;
}
//让设备进入休眠状态
int suspend_devices_and_enter(suspend_state_t state)
{
int error;
bool wakeup = false;
if (need_suspend_ops(state) && !suspend_ops)
return -ENOSYS;
trace_machine_suspend(state);
//检查平台代码是否需要提供以及是否提供了suspend_ops
//调用suspend_ops的begin回调(有的话),通知平台代码,以便让其作相应的处理(需要的话)。可能失败,需要跳至Close处执行恢复操作(suspend_ops->end)。
if (need_suspend_ops(state) && suspend_ops->begin) {
error = suspend_ops->begin(state);
if (error)
goto Close;
}
suspend_console();//停止串口
ftrace_stop();
suspend_test_start();
//dpm_suspend_start中会调用dpm_prepare(state)和dpm_suspend(state)两个函数
/*1.dpm_prepare(state)中,对于dpm_list链表中的每一个设备都调用device_prepare,即准备阶段
即对于每一个涉笔调用它的dev->pm_domain->ops->suspend_noirq 或
dev->type->pm->suspend_noirq 或
dev->class->pm->suspend_noirq 或
dev->bus->pm->suspend_noirq 或
dev->driver->pm->suspend_noirq */
/*2.dpm_suspend(state)中,让各类设备休眠
将准备好的设备放入dpm_prepared_list中,对于dpm_prepared_list链表中的每一个设备,都调用device_suspend(dev);
__device_suspend(dev, pm_transition, false);
对于该设备,调用它的dev->pm_domain->ops->suspend 或
dev->type->pm->suspend 或
dev->class->pm->suspend 或
dev->bus->pm->suspend 或
dev->driver->pm->suspend*/
error = dpm_suspend_start(PMSG_SUSPEND);
if (error) {
printk(KERN_ERR "PM: Some devices failed to suspend\n");
//注意:调用dpm_suspend_start,调用所有设备的->prepare和->suspend回调函数,suspend需要正常suspend的设备。suspend device可能失败,需要跳至 Recover_platform,执行recover操作(suspend_ops->recover)。
goto Recover_platform;
}
suspend_test_finish("suspend devices");
if (suspend_test(TEST_DEVICES))
goto Recover_platform;
do {
error = suspend_enter(state, &wakeup);//让CPU进入休眠
//suspend_enter返回,如果返回原因不是发生错误,且不是wakeup事件。则调用suspend_ops的suspend_again回调,检查是否需要再次suspend。再什么情况下要再次suspend呢?需要看具体的平台了。
} while (!error && !wakeup && need_suspend_ops(state)
&& suspend_ops->suspend_again && suspend_ops->suspend_again());//
Resume_devices:
suspend_test_start();
dpm_resume_end(PMSG_RESUME);
suspend_test_finish("resume devices");
ftrace_start();
resume_console();//回复串口
Close:
if (need_suspend_ops(state) && suspend_ops->end)
suspend_ops->end();
trace_machine_suspend(PWR_EVENT_EXIT);
return error;
Recover_platform:
if (need_suspend_ops(state) && suspend_ops->recover)
suspend_ops->recover();
goto Resume_devices;
}
suspend_ops是何方神圣呢?
我们可以找到他的赋值
void suspend_set_ops(const struct platform_suspend_ops *ops)
{
suspend_state_t i;
int j = 0;
lock_system_sleep();
suspend_ops = ops; //给suspend_ops赋值
for (i = PM_SUSPEND_MEM; i >= PM_SUSPEND_STANDBY; i--)
if (valid_state(i)) {
pm_states[i] = pm_labels[j++];
} else if (!relative_states) {
pm_states[i] = NULL;
j++;
}
pm_states[PM_SUSPEND_FREEZE] = pm_labels[j];
unlock_system_sleep();
}
suspend的最终目的,是让系统进入可恢复的挂起状态,而该功能必须有平台相关代码的参与才能完成,因此内核PM Core就提供了一系列的回调函数(封装在platform_suspend_ops中),让平台代码(如arch/arm/mach-xxx/pm.c)实现,然后由PM Core在合适的时机调用。这些回调函数包含一个valid函数,就是用来告知PM Core,支持哪些state。
例如:
在arch\arm\plat-samsung\pm.c
s3c_pm_init函数中
suspend_set_ops(&s3c_pm_ops);
suspend_ops = ops
例如:
static struct pm_ops s3c2410_pm_ops = {
.pm_disk_mode = PM_DISK_FIRMWARE,
.prepare = s3c2410_pm_prepare,
.enter = s3c2410_pm_enter,
.finish = s3c2410_pm_finish,
};
以上都是suspend前的准备工作,此时,调用suspend_enter接口,使系统进入指定的电源状态。该接口的内容如下:
//位于kernel\power\suspend.c
static int suspend_enter(suspend_state_t state, bool *wakeup)
{
int error;
//调用suspend_ops的prepare回调(有的话),通知平台代码,以便让其在即将进行状态切换之时,再做一些处理(需要的话)。该回调可能失败(平台代码出现意外),失败的话,需要跳至Platform_finish处,调用suspend_ops的finish回调,执行恢复操作。
error = platform_suspend_prepare(state);
if (error)
goto Platform_finish;
/*dpm_suspend_late(state); (drivers/base/power/main.c)
将上述中休眠后的设备加到dpm_suspended_list链表中,对于dpm_suspended_list链表中的每一个设备,都调用device_suspend_late(dev, state),做休眠后的清理。
对于该设备,调用它的dev->pm_domain->ops->suspend_late 或
dev->type->pm->suspend_late 或
dev->class->pm->suspend_late 或
dev->bus->pm->suspend_late 或
dev->driver->pm->suspend_late */
error = dpm_suspend_late(PMSG_SUSPEND);
if (error) {
printk(KERN_ERR "PM: late suspend of devices failed\n");
goto Platform_finish;
}
//调用suspend_ops的prepare_late回调(有的话),通知平台代码,以便让其在最后关头,再做一些处理(需要的话)。该回调可能失败(平台代码出现意外),失败的话,需要跳至Platform_wake处,调用suspend_ops的wake回调,执行device的resume、调用suspend_ops的finish回调,执行恢复操作。
error = platform_suspend_prepare_late(state);
if (error)
goto Devices_early_resume;
error = dpm_suspend_noirq(PMSG_SUSPEND);
if (error) {
printk(KERN_ERR "PM: noirq suspend of devices failed\n");
goto Platform_early_resume;
}
error = platform_suspend_prepare_noirq(state);
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) {
trace_suspend_resume(TPS("machine_suspend"), state, true);
freeze_enter();
trace_suspend_resume(TPS("machine_suspend"), state, false);
goto Platform_wake;
}
//调用disable_nonboot_cpus,禁止所有的非boot cpu。也会失败,执行恢复操作即可。
error = disable_nonboot_cpus();
if (error || suspend_test(TEST_CPUS))
goto Enable_cpus;
//调用arch_suspend_disable_irqs,关全局中断。如果无法关闭,则为bug。
arch_suspend_disable_irqs();
BUG_ON(!irqs_disabled());
//关闭核心模块
error = syscore_suspend();
if (!error) {
*wakeup = pm_wakeup_pending();
if (!(suspend_test(TEST_CORE) || *wakeup)) {
trace_suspend_resume(TPS("machine_suspend"),
state, true);
//调用suspend_ops的enter回调,进行状态切换。这时,系统应该已经suspend了..。这里会调用平台的enter接口,里面会
//1.配置唤醒中断 2.设置TLB 3.自刷新ram 4.设置CLKCON的SLEEP 5.设置寄存器返回值等
error = suspend_ops->enter(state);
trace_suspend_resume(TPS("machine_suspend"),
state, false);
events_check_enabled = false;
}
syscore_resume();//这里开始唤醒
}
arch_suspend_enable_irqs();//开中断
BUG_ON(irqs_disabled());
Enable_cpus:
enable_nonboot_cpus();//唤醒后开nonboot cpu
Platform_wake:
//suspend_ops->wake
platform_resume_noirq(state);
/*对于dpm_noirq_list链表中的每一个设备,调用device_resume_noirq(dev, state);
对于该设备,调用它的dev->pm_domain->ops->resume_noirq 或
dev->type->pm->resume_noirq 或
dev->class->pm->resume_noirq 或
dev->bus->pm->resume_noirq 或
dev->driver->pm->resume_noirq */
dpm_resume_noirq(PMSG_RESUME);
Platform_early_resume:
platform_resume_early(state);
Devices_early_resume:
/*对于dpm_late_early_list链表中的每一个设备,调用device_resume_early(dev, state);
对于该设备,调用它的dev->pm_domain->ops->resume_early 或
dev->type->pm->resume_early 或
dev->class->pm->resume_early 或
dev->bus->pm->resume_early 或
dev->driver->pm->resume_early */
dpm_resume_early(PMSG_RESUME);
Platform_finish:
platform_resume_finish(state);
return error;
}
唤醒后,会返回,继续resume操作,resume device、start ftrace、resume console、suspend_ops->end等等。
suspend_finish中
static void suspend_finish(void)
{
suspend_thaw_processes();
pm_notifier_call_chain(PM_POST_SUSPEND);
pm_restore_console();
}
a)恢复所有的用户空间进程和内核线程等app
b)发送suspend结束的通知。
c)将console切换回原来的。
驱动程序里相关的电源管理函数的调用过程:
休眠: prepare—>suspend—>suspend_late—>suspend_noirq
唤醒: resume_noirq—>resume_early—>resume–>complete
整个休眠流程框架如下:
------------------------------
state_store (kernel/power/main.c)
pm_suspend (kernel/power/suspend.c)
enter_state (kernel/power/suspend.c)
suspend_prepare (kernel/power/suspend.c)
pm_prepare_console (kernel/power/console.c)
pm_notifier_call_chain(PM_SUSPEND_PREPARE); (kernel/power/main.c) // 通知所有关心"休眠消息"的驱动程序
suspend_freeze_processes (kernel/power/power.h) // 冻结APP和内核线程
suspend_devices_and_enter (kernel/power/suspend.c) // 让设备进入休眠状态
suspend_ops->begin // 如果平台相关的代码有begin函数就去调用它
suspend_console (kernel/power/suspend.c)
dpm_suspend_start(PMSG_SUSPEND); (drivers/base/power/main.c)
dpm_prepare(state); (drivers/base/power/main.c)
对于dmp_list链表中的每一个设备,都调用device_prepare(dev, state);
对于该设备,调用它的dev->pm_domain->ops->prepare 或
dev->type->pm->prepare 或
dev->class->pm->prepare 或
dev->bus->pm->prepare 或
dev->driver->pm->prepare
dpm_suspend(state); (drivers/base/power/main.c) // 让各类设备休眠
对于dpm_prepared_list链表中的每一个设备,都调用device_suspend(dev);
__device_suspend(dev, pm_transition, false);
对于该设备,调用它的dev->pm_domain->ops->suspend 或
dev->type->pm->suspend 或
dev->class->pm->suspend 或
dev->bus->pm->suspend 或
dev->driver->pm->suspend
suspend_enter(state, &wakeup) (kernel/power/suspend.c)
suspend_ops->prepare // 即s3c_pm_prepare
dpm_suspend_end(PMSG_SUSPEND); (drivers/base/power/main.c)
dpm_suspend_late(state); (drivers/base/power/main.c)
对于dpm_suspended_list链表中的每一个设备,都调用device_suspend_late(dev, state);
对于该设备,调用它的dev->pm_domain->ops->suspend_late 或
dev->type->pm->suspend_late 或
dev->class->pm->suspend_late 或
dev->bus->pm->suspend_late 或
dev->driver->pm->suspend_late
dpm_suspend_noirq
对于dpm_late_early_list链表中的每一个设备,都调用device_suspend_noirq(dev, state);
对于该设备,调用它的dev->pm_domain->ops->suspend_noirq 或
dev->type->pm->suspend_noirq 或
dev->class->pm->suspend_noirq 或
dev->bus->pm->suspend_noirq 或
dev->driver->pm->suspend_noirq
suspend_ops->prepare_late() //
disable_nonboot_cpus(); // 由于有多核cpu,关掉不是启动的cpu
arch_suspend_disable_irqs();//关闭中断
syscore_suspend; // 关闭核心模块
suspend_ops->enter(state); // s3c_pm_enter (arch\arm\plat-samsung\pm.c)
......
pm_cpu_prep // s3c2410_pm_prepare (arch\arm\mach-s3c24xx\pm-s3c2410.c)
GSTATUS3 = s3c_cpu_resume
......
cpu_suspend(0, pm_cpu_sleep); // arch\arm\kernel\sleep.S
pm_cpu_sleep (arch\arm\mach-s3c24xx\pm-s3c2410.c) // s3c2410_cpu_suspend
s3c2410_cpu_suspend (arch\arm\mach-s3c24xx\sleep-s3c2410.S)
以上是休眠过程
===================================
下面开始唤醒过程
按键, 导致u-boot运行, 读取GSTATUS3, 执行s3c_cpu_resume
.....
s3c_pm_restore_core
syscore_resume
arch_suspend_enable_irqs
enable_nonboot_cpus
suspend_ops->wake
dpm_resume_start(PMSG_RESUME);
dpm_resume_noirq(state);
对于dpm_noirq_list链表中的每一个设备,调用device_resume_noirq(dev, state);
对于该设备,调用它的dev->pm_domain->ops->resume_noirq 或
dev->type->pm->resume_noirq 或
dev->class->pm->resume_noirq 或
dev->bus->pm->resume_noirq 或
dev->driver->pm->resume_noirq
dpm_resume_early(state);
对于dpm_late_early_list链表中的每一个设备,调用device_resume_early(dev, state);
对于该设备,调用它的dev->pm_domain->ops->resume_early 或
dev->type->pm->resume_early 或
dev->class->pm->resume_early 或
dev->bus->pm->resume_early 或
dev->driver->pm->resume_early
suspend_ops->finish()
s3c_pm_finish
dpm_resume_end(PMSG_RESUME);
resume_console();
suspend_finish();
suspend_thaw_processes();
pm_notifier_call_chain(PM_POST_SUSPEND);
pm_restore_console();
//返回用户空间