Linux电源管理 Suspend

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过程的同步问题。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值