linux内核提供了三种Suspend, Freeze, Standby和STR(Suspend to RAM), 在用户空间向“/sys/power/state"文件分别写入”freeze“, ”standby"和“mem", 即可触发他们。
内核中,Suspend及resume过程世纪到PM core, Device PM, 各个设备的驱动, Platform dependent PM, CPU control等多个模块,涉及了console switch, process freeze, CPU hotplug, wakeup处理多个知识点。
Suspend功能有关的代码分布
内核中suspend功能相关的代码包括PM core, device PM, Platform等几大块,具体如下:
1) PM Core
kernel/power/main.c----提供用户空间接口(/sys/power/state)
kernel/power/suspend.c----Suspend功能的主逻辑
kernel/power/suspend_test.c----Suspend功能的测试逻辑
kernel/power/console.c----Suspend过程中对控制台的处理逻辑
kernel/power/process.c----Suspend过程中对进程的处理逻辑
2)Device PM
drivers/base/power/*----PM-RPM Linux电源管理(二)
设备驱动----具体设备驱动的位置,不再涉及。
3)Platform dependent PM
include/linux/suspend.h----定义platform dependent PM有关的操作函数集
arch/xxx/mach-xxx/xxx.c或者
arch/xxx/plat-xxx/xxx.c----平台相关的电源管理操作
suspend & resume 过程概述
下面图片对Linux suspend&resume过程做了一个概述, 读者可以顺着这个流程阅读内核源代码。 具体的说明,可以参考后面的代码分析。
代码分析
- suspend入口
在用户空间执行如下操作
echo "freeze" > /sys/power/state
echo "standby" > /sys/power/state
echo "mem" > /sys/power/state
会通过sysfs触发suspend的执行,相应的处理代码如下:
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(); //先lock住autosleep
if (error)
return error;
if (pm_autosleep_state() > PM_SUSPEND_ON) {
error = -EBUSY;
goto out;
}
//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)
根据state的值,如果不是PM_SUSPEND_MAX(对应hibernate功能), 则调用pm_suspend接口,进行后续的处理。
state = decode_state(buf, n); //解析用户传入的buffer(freeze, standby or mem)
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;
}
pm_suspend在/kernel/power/suspend.c定义, 处理所有的suspend流程。
- pm_suspend & enter state
pm_suspend的实现非常简单,简单做一下参数合法性判断,直接调用enter_state接口,如下:
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;
enter_state的代码为:
static int enter_state(suspend_state_t state)
{
int error;
trace_suspend_resume(TPS("suspend_enter"), state, true);
if (state == PM_SUSPEND_FREEZE) {
#ifdef CONFIG_PM_DEBUG
if (pm_test_level != TEST_NONE && pm_test_level <= TEST_CPUS) {
pr_warn("PM: Unsupported test mode for suspend to idle, please choose none/freezer/devices/platform.\n");
return -EAGAIN;
}
#endif
} else if (!valid_state(state)) { //调用valid_state,判断该平台是否支持该电源状态
return -EINVAL;
}
if (!mutex_trylock(&pm_mutex)) //加互斥锁处理,只允许一个实例处理suspend
return -EBUSY;
//如果state是freeze, 调用freeze_begin, 进行suspend to freeze相关的特殊动作。
if (state == PM_SUSPEND_FREEZE)
freeze_begin();
#ifndef CONFIG_SUSPEND_SKIP_SYNC
trace_suspend_resume(TPS("sync_filesystems"), 0, true);
pr_info("PM: Syncing filesystems ... ");
sys_sync();
pr_cont("done.\n");
trace_suspend_resume(TPS("sync_filesystems"), 0, false);
#endif
pr_debug("PM: Preparing system for sleep (%s)\n", pm_states[state]);
pm_suspend_clear_flags();
error = suspend_prepare(state);
if (error)
goto Unlock;
if (suspend_test(TEST_FREEZER))
goto Finish;
trace_suspend_resume(TPS("suspend_enter"), state, false);
pr_debug("PM: Suspending system (%s)\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;
}
主要工作包括:
1)调用valid_state,判断该平台是否支持该电源状态
suspend的最终目的,是让系统进入可恢复的挂起状态,而该功能必须有平台相关代码的参与才能完成, 因此内核PM Core就提供了一些列的回调函数(封装在platform_suspend_ops中), 让平台(比如S5pv210)代码(如arch/arm/mach-xxx/pm.c)实现, 然后由PM Core再合适的时机调用。这些回调函数包含一个valid函数, 就是用来告知PM Core, 支持哪些state.
value_state的实现:
static bool valid_state(suspend_state_t state)
{
/*
* PM_SUSPEND_STANDBY and PM_SUSPEND_MEM states need low level
* support and need to be valid to the low level
* implementation, no valid callback implies that none are valid.
*/
return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
}
需要调用suspend_ops的valid回调,由底层平台代码判断是否支持
2) 加互斥锁处理,只允许一个实例处理suspend
3)如果state是freeze, 调用freeze_begin, 进行suspend to freeze相关的特殊动作。
4) 打印提示信息,同步文件系统
5) 调用
, 进行suspend前的准备, 主要包括 switch console和process & thread freezing。 如果失败, 则终止suspend过程。
6)然后调用suspend_devices_and_enter接口,该接口负责suspend和resume的实际动作。 前半部分,suspend control, suspend device, 关中断, 调用平台相关的suspend_ops使系统进入低功耗模式。 后半部分, 在系统被事件唤醒后,处理相关动作,调用平台相关的suspend_ops恢复系统,开中断, resume device, resume console。
7)最后,调用suspend finish, 恢复(或等待恢复) process & thread, 还原console
- suspend_prepare
suspend_prepare的代码如下:
static int suspend_prepare(suspend_state_t state)
{
int error, nr_calls = 0;
if (!sleep_state_supported(state))
return -EPERM;
pm_prepare_console();
error = __pm_notifier_call_chain(PM_SUSPEND_PREPARE, -1, &nr_calls);
if (error) {
nr_calls--;
goto Finish;
}
trace_suspend_resume(TPS("freeze_processes"), 0, true);
error = suspend_freeze_processes();
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, nr_calls, NULL);
pm_restore_console();
return error;
}
主要工作为:
1)检查suspend_ops是否提供了.enter的回调和当前状态是否是PM_SUSPEND_FREEZE, 没有的话,返回错误
2)调用pm_prepare_console, 将当前的console切换到一个虚拟的console并重定向到内核的kmsg(需要的话), 该功能称作TV switch, 后面我会在稍微详细的介绍一下, 但Linux控制台子系统是相当复杂的, 更具体的分析,要在控制台子系统的分析文章中说明。
3)调用pm_notifier_call_chain, 发送suspend开始的消息(PM_SUSPEND_PREPARE), 后面会详细介绍
4)调用suspend_freeze_process, freeze用户空间进程和一些内核线程。该功能称作freezing-of-tasks
5)如果freezing-of-tasks失败,调用pm_restore-console, 将console切换回原来的console,并返回错误, 以便能终止suspend.
- suspend_devices_and_enter
suspend_device_and_enter的过程 较为复杂,代码实现如下:
int suspend_devices_and_enter(suspend_state_t state)
{
int error;
bool wakeup = false;
if (!sleep_state_supported(state))
return -ENOSYS;
error = platform_suspend_begin(state);
if (error)
goto Close;
suspend_console();
suspend_test_start();
error = dpm_suspend_start(PMSG_SUSPEND);
if (error) {
pr_err("PM: Some devices failed to suspend, or early wake event detected\n");
goto Recover_platform;
}
suspend_test_finish("suspend devices");
if (suspend_test(TEST_DEVICES))
goto Recover_platform;
do {
error = suspend_enter(state, &wakeup);
} while (!error && !wakeup && platform_suspend_again(state));
Resume_devices:
suspend_test_start();
dpm_resume_end(PMSG_RESUME);
suspend_test_finish("resume devices");
trace_suspend_resume(TPS("resume_console"), state, true);
resume_console();
trace_suspend_resume(TPS("resume_console"), state, false);
Close:
platform_resume_end(state);
return error;
Recover_platform:
platform_recover(state);
goto Resume_devices;
}
1)检查suspend_ops是否提供了.enter的回调和当前状态是否是PM_SUSPEND_FREEZE, 没有的话,返回错误
2)如果状态是freeze,就调用freeze_ops->begin,否则调用suspend_ops的begin回调(有的话), 通知平台代码,以便让其作相应的处理(需要的话), 可能失败,需要跳至Close处执行恢复操作(suspend_ops->end)。
3) 调用suspend_control, 挂起control.该接口由"kernel/printk.c"实现,主要是hold一个lock, 该lock会阻止其他代码访问control.
4)调用dpm_suspend_start, 调用所有的设备的->prepare和suspend的回调函数, suspend需要正常的suspend的设备,,suspend device可能失败,需要跳至Recover_platform, 执行recover操作(suspend_ops->recover).
5) 以上都是suspend前的准备工作,此时调用suspend_enter接口,使系统进入指定的 电源状态。 该接口的内容如下:
static int suspend_enter(suspend_state_t state, bool *wakeup)
{
int error;
error = platform_suspend_prepare(state);
if (error)
goto Platform_finish;
error = dpm_suspend_late(PMSG_SUSPEND);
if (error) {
pr_err("PM: late suspend of devices failed\n");
goto Platform_finish;
}
error = platform_suspend_prepare_late(state);
if (error)
goto Devices_early_resume;
error = dpm_suspend_noirq(PMSG_SUSPEND);
if (error) {
pr_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;
}
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)) {
trace_suspend_resume(TPS("machine_suspend"),
state, true);
error = suspend_ops->enter(state);
trace_suspend_resume(TPS("machine_suspend"),
state, false);
events_check_enabled = false;
} else if (*wakeup) {
error = -EBUSY;
}
syscore_resume();
}
arch_suspend_enable_irqs();
BUG_ON(irqs_disabled());
Enable_cpus:
enable_nonboot_cpus();
Platform_wake:
platform_resume_noirq(state);
dpm_resume_noirq(PMSG_RESUME);
Platform_early_resume:
platform_resume_early(state);
Devices_early_resume:
dpm_resume_early(PMSG_RESUME);
Platform_finish:
platform_resume_finish(state);
return error;
}
1) 该接口处理完后,会通过返回值告知是否enter成功,同时通过wakeup指针,告知调用者,是否有wakeup事件发生, 导致电源状态切换失败。
2) 调用suspend_ops的prepare回调(有的话), 状态不为freeze的时候调用suspend_ops->prepare,通知平台代码, 以便让其在即将进行状态切换之时,再做一些处理(需要的话)。 该回调可能失败(平台代码出现意外), 失败的话,需要跳至Platform_finish处,调用suspend_ops的finish回调,执行恢复操作。
3) 调用dpm_suspend_late, 取出所有dpm_suspended_list链表中的device,执行devcies_suspend_late。
4)调用platform_suspend_prepare_late, 判断状态是否是freeze, 如果是,同时包含有freeze_ops和freeze_ops->prepare, 就执行freeze_ops->prepare,否则直接返回0
5) 调用dpm_suspend_noirq,调用所有device的suspned_noirq
6)调用platform_suspend_prepare_noirq
7)调用disable_nonboot_cpus, 禁止所有的非boot cpu, 也会失败,恢复操作即可
8) 调用arch_suspend_disable_irqs, 关全局中断。如果无法关闭,则为bug
9) 调用syscore_suspend, suspend system core。同样会失败,执行恢复操作即可。
10) 如果很幸运,以上操作都成功了, 那么,切换吧。 不过,别高兴太早, 还得调用pm_wakeup_pending检查一下, 这段时间内,是否有唤醒事件发生, 如果有就要终止suspend.
11) 如果一切顺利,调用suspend_ops的enter回调,进行状态切换,此时系统应该已经suspend
12) suspend 过程中,唤醒事件发生,系统唤醒, 该函数接着执行resume操作, 并最终返回。 resume动作基本上是suspend的反动作,就不再继续分析了。
13) 或者,由于意外, suspend终止, 该函数也会返回
13) suspend_enter返回,如果并返回原因不是发生错误,且不是wakeup事件 。 则调用suspend_ops的suspend_again回调,检查是否需要再次suspend。
14) 继续resume操作,resume devcie, start ftrace, resume console, suspend_ops->end==
15) 该函数返回后,表示系统已经resume
vt switch
通常行情况下,系统的控制台模块(driver\tty\vt)会在suspend过程中,重新分配一个console, 并将控制台切换到该console上。 然后resume时,切换回旧的console。 这就是VT switch功能。 VT switch是很耗时的, 因此内核提供了一些机制,控制是否使用这些功能:
1) 提供了一个接口函数pm_set_vt_switch(drivers\tty\vt\vi_ioctl.c), 方便其他内核模块从整体上关闭或者开启VT功能。
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: 负责在需要TV switch时,将console切换到SUSPEND console。
suspend console: 挂起console ,由kernel/printk.c实现,主要是hold住 console用的互斥锁,使他人无法使用console.
resume console: 对console解锁
restore console: 将console恢复为初始的console
freezing of task
进程的freezing功能,是suspend, hibernate等电源管理功能的组成部分,在新版内核中,它被独立出来,作为一个独立的电源管理状态(freeze). 该功能的目的,是在电源管理的状态切换过程中,确保所有用户空间程序和部分内核线程处在一个稳定的状态。
device Pm ops和platform PM ops的调用时机
对Linux驱动工程师来说, device PM ops和platform PM ops是电源管理的全部,只要在合适的地方,实现合适的回调函数,即可实现系统的电源管理。 但现实太复杂了, 以至于kernel提供的这两个数据结构也很复杂,
struct dev_pm_ops {dev_pm_domain
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);
};
虽然内核的注释已经相当详细了,但我们一定会犯晕,到底该实现哪些回调?这些回调的应用场景又是什么?除此之外,我们可以总结一下,在电源状态切换时,这些回调的调用时机,可以从侧面帮助理解。如下:只介绍和suspend功能有关的,struct dev_pm_ops简称D,struct platform_suspend_ops简称P):
suspend过程的同步和PM wakeup
最重要的事情,如果suspend的过程中,有唤醒事件产生怎么办?正常的流程应该终止suspend,返回并处理事件。 但由于suspend过程的特殊性, 进程被freeze,关中断等等,导致事情没那么简单,以至于在很久的一段时间内,kernel都不能很好的处理,这也称作suspend过程的同步问题。