suspend/resume流程

发布时间:2024年01月13日

Linux的suspend机制是一种节能技术,用于将计算机的当前状态保存起来,然后进入休眠状态,以节省能源。在休眠状态下,计算机的硬件设备会停止工作,而保存的计算机状态被保存在内存或磁盘中。在用户空间向“/sys/power/state”文件分别写入“freeze”、“standby”和“mem”,即可触发它们。

例如:echo?mem > /sys/power/state即可触发

流程图:

/sys/power/state节点在Main.c (kernel4.14\kernel\power)中实现,当向/sys/power/state节点中写入时即会调用如下函数state_store:

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) {
?? ??? ?if (state == PM_SUSPEND_MEM)
?? ??? ??? ?state = mem_sleep_current;

?? ??? ?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);

从上面state_store函数分析,调用pm_suspend进入suspend

Suspend.c (kernel4.14\kernel\power)?

int pm_suspend(suspend_state_t state)
{
?? ?int error;

?? ?if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
?? ??? ?return -EINVAL;

?? ?pr_info("suspend entry (%s)\n", mem_sleep_labels[state]);
?? ?error = enter_state(state);
?? ?if (error) {
?? ??? ?suspend_stats.fail++;
?? ??? ?dpm_save_failed_errno(error);
?? ?} else {
?? ??? ?suspend_stats.success++;
?? ?}
?? ?pr_info("suspend exit\n");
?? ?return error;
}

pm_suspend主要调用enter_state函数
?

static int enter_state(suspend_state_t state)
{
?? ?int error;

?? ?trace_suspend_resume(TPS("suspend_enter"), state, true);
?? ?if (state == PM_SUSPEND_TO_IDLE) {
#ifdef CONFIG_PM_DEBUG
?? ??? ?if (pm_test_level != TEST_NONE && pm_test_level <= TEST_CPUS) {
?? ??? ??? ?pr_warn("Unsupported test mode for suspend to idle, please choose none/freezer/devices/platform.\n");
?? ??? ??? ?return -EAGAIN;
?? ??? ?}
#endif
?? ?} else if (!valid_state(state)) {
?? ??? ?return -EINVAL;
?? ?}
?? ?if (!mutex_trylock(&pm_mutex))
?? ??? ?return -EBUSY;

?? ?if (state == PM_SUSPEND_TO_IDLE)
?? ??? ?s2idle_begin();

#ifndef CONFIG_SUSPEND_SKIP_SYNC
?? ?trace_suspend_resume(TPS("sync_filesystems"), 0, true);
?? ?pr_info("Syncing filesystems ... ");
?? ?sys_sync();
?? ?pr_cont("done.\n");
?? ?trace_suspend_resume(TPS("sync_filesystems"), 0, false);
#endif

?? ?pm_pr_dbg("Preparing system for sleep (%s)\n", mem_sleep_labels[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);
?? ?pm_pr_dbg("Suspending system (%s)\n", mem_sleep_labels[state]);
?? ?pm_restrict_gfp_mask();
?? ?error = suspend_devices_and_enter(state);
?? ?pm_restore_gfp_mask();

?Finish:
?? ?events_check_enabled = false;
?? ?pm_pr_dbg("Finishing wakeup.\n");
?? ?suspend_finish();
?Unlock:
?? ?mutex_unlock(&pm_mutex);
?? ?return error;
}

从上:

suspend_prepare主要做休眠前的准备工作

suspend_devices_and_enter设备进入休眠

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;
}

pm_prepare_console函数的主要作用是在系统进入睡眠状态(suspend)之前,准备控制台以使其能够在唤醒时正常工作。

__pm_notifier_call_chain通知一些设备做睡眠前的准备工作

suspend_freeze_processes 函数来挂起所有用户空间进程和kernel线程。挂起进程的目的是确保在睡眠过程中,没有任何进程在访问或修改系统资源,从而避免数据不一致或冲突的情况发生。

suspend_devices_and_enter函数

int suspend_devices_and_enter(suspend_state_t state)
{
	int error;
	bool wakeup = false;

	if (!sleep_state_supported(state))
		return -ENOSYS;

	pm_suspend_target_state = state;

	error = platform_suspend_begin(state);
	if (error)
		goto Close;

	suspend_console();
	suspend_test_start();
	error = dpm_suspend_start(PMSG_SUSPEND);
	if (error) {
		pr_err("Some devices failed to suspend, or early wake event detected\n");
		log_suspend_abort_reason("Some devices failed to suspend, or early wake event detected");
		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);
	pm_suspend_target_state = PM_SUSPEND_ON;
	return error;

 Recover_platform:
	platform_recover(state);
	goto Resume_devices;
}

platform_suspend_begin平台suspend 开始,一般是空

suspend_console暂停控制台子系统

suspend_test_start 是空

dpm_suspend_start 所有设备的pm suspend回调,如果某个外设的suspend回调失败,可以通过看出这个函数往下追踪log进行去定位

suspend_enter使系统进入给定的睡眠状态

int dpm_suspend_start(pm_message_t state)
{
	int error;

	error = dpm_prepare(state);
	if (error) {
		suspend_stats.failed_prepare++;
		dpm_save_failed_step(SUSPEND_PREPARE);
	} else
		error = dpm_suspend(state);
	return error;
}

dpm_prepare用于设备系统电源转换为prepare,该函数将从dpm_list中获取设备,然后通过device_prepare函数调用pm prepare回调,并帮prepare的设备放到dpm_prepared_list链表中

int dpm_prepare(pm_message_t state)
{
?? ?int error = 0;

?? ?trace_suspend_resume(TPS("dpm_prepare"), state.event, true);
?? ?might_sleep();

?? ?/*
?? ? * Give a chance for the known devices to complete their probes, before
?? ? * disable probing of devices. This sync point is important at least
?? ? * at boot time + hibernation restore.
?? ? */
?? ?wait_for_device_probe();
?? ?/*
?? ? * It is unsafe if probing of devices will happen during suspend or
?? ? * hibernation and system behavior will be unpredictable in this case.
?? ? * So, let's prohibit device's probing here and defer their probes
?? ? * instead. The normal behavior will be restored in dpm_complete().
?? ? */
?? ?device_block_probing();

?? ?mutex_lock(&dpm_list_mtx);
?? ?while (!list_empty(&dpm_list)) {
?? ??? ?struct device *dev = to_device(dpm_list.next);

?? ??? ?get_device(dev);
?? ??? ?mutex_unlock(&dpm_list_mtx);

?? ??? ?trace_device_pm_callback_start(dev, "", state.event);
?? ??? ?error = device_prepare(dev, state);
?? ??? ?trace_device_pm_callback_end(dev, error);

?? ??? ?mutex_lock(&dpm_list_mtx);
?? ??? ?if (error) {
?? ??? ??? ?if (error == -EAGAIN) {
?? ??? ??? ??? ?put_device(dev);
?? ??? ??? ??? ?error = 0;
?? ??? ??? ??? ?continue;
?? ??? ??? ?}
?? ??? ??? ?printk(KERN_INFO "PM: Device %s not prepared "
?? ??? ??? ??? ?"for power transition: code %d\n",
?? ??? ??? ??? ?dev_name(dev), error);
?? ??? ??? ?put_device(dev);
?? ??? ??? ?break;
?? ??? ?}
?? ??? ?dev->power.is_prepared = true;
?? ??? ?if (!list_empty(&dev->power.entry))
?? ??? ??? ?list_move_tail(&dev->power.entry, &dpm_prepared_list);
?? ??? ?put_device(dev);
?? ?}
?? ?mutex_unlock(&dpm_list_mtx);
?? ?trace_suspend_resume(TPS("dpm_prepare"), state.event, false);
?? ?return error;
}

dpm_suspend用于设备系统电源转换为suspend,该函数将从dpm_prepared_list中获取设备,然后通过device_suspend函数调用pm suspend回调,并帮suspend的设备放到dpm_suspended_list链表中

int dpm_suspend(pm_message_t state)
{
?? ?ktime_t starttime = ktime_get();
?? ?int error = 0;

?? ?trace_suspend_resume(TPS("dpm_suspend"), state.event, true);
?? ?might_sleep();

?? ?cpufreq_suspend();

?? ?mutex_lock(&dpm_list_mtx);
?? ?pm_transition = state;
?? ?async_error = 0;
?? ?while (!list_empty(&dpm_prepared_list)) {
?? ??? ?struct device *dev = to_device(dpm_prepared_list.prev);

?? ??? ?get_device(dev);
?? ??? ?mutex_unlock(&dpm_list_mtx);

?? ??? ?error = device_suspend(dev);

?? ??? ?mutex_lock(&dpm_list_mtx);
?? ??? ?if (error) {
?? ??? ??? ?pm_dev_err(dev, state, "", error);
?? ??? ??? ?dpm_save_failed_dev(dev_name(dev));
?? ??? ??? ?put_device(dev);
?? ??? ??? ?break;
?? ??? ?}
?? ??? ?if (!list_empty(&dev->power.entry))
?? ??? ??? ?list_move(&dev->power.entry, &dpm_suspended_list);
?? ??? ?put_device(dev);
?? ??? ?if (async_error)
?? ??? ??? ?break;
?? ?}
?? ?mutex_unlock(&dpm_list_mtx);
?? ?async_synchronize_full();
?? ?if (!error)
?? ??? ?error = async_error;
?? ?if (error) {
?? ??? ?suspend_stats.failed_suspend++;
?? ??? ?dpm_save_failed_step(SUSPEND_SUSPEND);
?? ?} else
?? ??? ?dpm_show_time(starttime, state, NULL);
?? ?trace_suspend_resume(TPS("dpm_suspend"), state.event, false);
?? ?return error;
}
?

suspend_enter函数如下:

static int suspend_enter(suspend_state_t state, bool *wakeup)
{
	char suspend_abort[MAX_SUSPEND_ABORT_LEN];
	int error, last_dev;

	error = platform_suspend_prepare(state);
	if (error)
		goto Platform_finish;

	error = dpm_suspend_late(PMSG_SUSPEND);
	if (error) {
		last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1;
		last_dev %= REC_FAILED_NUM;
		pr_err("late suspend of devices failed\n");
		log_suspend_abort_reason("%s device failed to power down",
			suspend_stats.failed_devs[last_dev]);
		goto Platform_finish;
	}
	error = platform_suspend_prepare_late(state);
	if (error)
		goto Devices_early_resume;

	if (state == PM_SUSPEND_TO_IDLE && pm_test_level != TEST_PLATFORM) {
		s2idle_loop();
		goto Platform_early_resume;
	}

	error = dpm_suspend_noirq(PMSG_SUSPEND);
	if (error) {
		last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1;
		last_dev %= REC_FAILED_NUM;
		pr_err("noirq suspend of devices failed\n");
		log_suspend_abort_reason("noirq suspend of %s device failed",
			suspend_stats.failed_devs[last_dev]);
		goto Platform_early_resume;
	}
	error = platform_suspend_prepare_noirq(state);
	if (error)
		goto Platform_wake;

	if (suspend_test(TEST_PLATFORM))
		goto Platform_wake;

	error = disable_nonboot_cpus();
	if (error || suspend_test(TEST_CPUS)) {
		log_suspend_abort_reason("Disabling non-boot cpus failed");
		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);
		} else if (*wakeup) {
			pm_get_active_wakeup_sources(suspend_abort,
				MAX_SUSPEND_ABORT_LEN);
			log_suspend_abort_reason(suspend_abort);
			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;
}

下面对suspend_enter函数中各个函数进行简要分析:

platform_suspend_prepare函数如下:主要是当state状态不等于PM_SUSPEND_FREEZE 且看suspend_ops->prepare是否存在,若存在,则调用suspend_ops->prepare(),这个一般与平台有关,不同的平台此回调实现不一样。

static int platform_suspend_prepare(suspend_state_t state)
{
?? ?return state != PM_SUSPEND_FREEZE && suspend_ops->prepare ?
?? ??? ?suspend_ops->prepare() : 0;
}

dpm_suspend_late 调用设备的ops->suspend_late回调 ,该函数将从dpm_suspended_list中获取设备,然后通过device_suspend_late函数调用pm ops->suspend_late回调,并帮late suspend的设备放到dpm_late_early_list链表中

int dpm_suspend_late(pm_message_t state)
{
?? ?ktime_t starttime = ktime_get();
?? ?int error = 0;

?? ?trace_suspend_resume(TPS("dpm_suspend_late"), state.event, true);
?? ?mutex_lock(&dpm_list_mtx);
?? ?pm_transition = state;
?? ?async_error = 0;

?? ?while (!list_empty(&dpm_suspended_list)) {
?? ??? ?struct device *dev = to_device(dpm_suspended_list.prev);

?? ??? ?get_device(dev);
?? ??? ?mutex_unlock(&dpm_list_mtx);

?? ??? ?error = device_suspend_late(dev);

?? ??? ?mutex_lock(&dpm_list_mtx);
?? ??? ?if (!list_empty(&dev->power.entry))
?? ??? ??? ?list_move(&dev->power.entry, &dpm_late_early_list);

?? ??? ?if (error) {
?? ??? ??? ?pm_dev_err(dev, state, " late", error);
?? ??? ??? ?dpm_save_failed_dev(dev_name(dev));
?? ??? ??? ?put_device(dev);
?? ??? ??? ?break;
?? ??? ?}
?? ??? ?put_device(dev);

?? ??? ?if (async_error)
?? ??? ??? ?break;
?? ?}
?? ?mutex_unlock(&dpm_list_mtx);
?? ?async_synchronize_full();
?? ?if (!error)
?? ??? ?error = async_error;
?? ?if (error) {
?? ??? ?suspend_stats.failed_suspend_late++;
?? ??? ?dpm_save_failed_step(SUSPEND_SUSPEND_LATE);
?? ??? ?dpm_resume_early(resume_event(state));
?? ?} else {
?? ??? ?dpm_show_time(starttime, state, "late");
?? ?}
?? ?trace_suspend_resume(TPS("dpm_suspend_late"), state.event, false);
?? ?return error;
}

platform_suspend_prepare_late函数如下:主要是当state状态等于PM_SUSPEND_FREEZE 且看freeze_ops->prepare是否存在,若存在,则调用freeze_ops->prepare,这个一般与平台有关,不同的平台此回调实现不一样。

static int platform_suspend_prepare_late(suspend_state_t state)
{
?? ?return state == PM_SUSPEND_FREEZE && freeze_ops && freeze_ops->prepare ?
?? ??? ?freeze_ops->prepare() : 0;
}

dpm_suspend_noirq调用设备的ops->suspend_noirq回调 ,该函数将从dpm_late_early_list中获取设备,然后通过device_suspend_noirq函数调用pm ops->suspend_noirq回调,并帮设备放到dpm_noirq_list链表中

int dpm_suspend_noirq(pm_message_t state)
{
?? ?ktime_t starttime = ktime_get();
?? ?int error = 0;

?? ?trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, true);
?? ?cpuidle_pause();
?? ?device_wakeup_arm_wake_irqs();
?? ?suspend_device_irqs();
?? ?mutex_lock(&dpm_list_mtx);
?? ?pm_transition = state;
?? ?async_error = 0;

?? ?while (!list_empty(&dpm_late_early_list)) {
?? ??? ?struct device *dev = to_device(dpm_late_early_list.prev);

?? ??? ?get_device(dev);
?? ??? ?mutex_unlock(&dpm_list_mtx);

?? ??? ?error = device_suspend_noirq(dev);

?? ??? ?mutex_lock(&dpm_list_mtx);
?? ??? ?if (error) {
?? ??? ??? ?pm_dev_err(dev, state, " noirq", error);
?? ??? ??? ?dpm_save_failed_dev(dev_name(dev));
?? ??? ??? ?put_device(dev);
?? ??? ??? ?break;
?? ??? ?}
?? ??? ?if (!list_empty(&dev->power.entry))
?? ??? ??? ?list_move(&dev->power.entry, &dpm_noirq_list);
?? ??? ?put_device(dev);

?? ??? ?if (async_error)
?? ??? ??? ?break;
?? ?}
?? ?mutex_unlock(&dpm_list_mtx);
?? ?async_synchronize_full();
?? ?if (!error)
?? ??? ?error = async_error;

?? ?if (error) {
?? ??? ?suspend_stats.failed_suspend_noirq++;
?? ??? ?dpm_save_failed_step(SUSPEND_SUSPEND_NOIRQ);
?? ??? ?dpm_resume_noirq(resume_event(state));
?? ?} else {
?? ??? ?dpm_show_time(starttime, state, "noirq");
?? ?}
?? ?trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, false);
?? ?return error;
}
?

以上主要是休眠的过程,唤醒的过程主要是休眠的过程的相反动作,这里不做过多介绍了。

下面说一下休眠失败常遇到的情况

1、设备suspend失败

主要dpm_suspend_start返回失败,如下log可以找到对应的设备进行分析:

?PM: Device a600000.ssusb failed to suspend: error -16
?PM: Some devices failed to suspend, or early wake event detected

2、中断唤醒

可以通过如下函数打印log查看是哪个中断唤醒的

void pm_system_irq_wakeup(unsigned int irq_number)
{
?? ?struct irq_desc *desc;
?? ?const char *name = "null";

?? ?if (pm_wakeup_irq == 0) {
?? ??? ?if (msm_show_resume_irq_mask) {
?? ??? ??? ?desc = irq_to_desc(irq_number);
?? ??? ??? ?if (desc == NULL)
?? ??? ??? ??? ?name = "stray irq";
?? ??? ??? ?else if (desc->action && desc->action->name)
?? ??? ??? ??? ?name = desc->action->name;

?? ??? ??? ?pr_warn("%s: %d triggered %s\n", __func__,
?? ??? ??? ??? ??? ?irq_number, name);

?? ??? ?}
?? ??? ?pm_wakeup_irq = irq_number;
?? ??? ?pm_system_wakeup();
?? ?}
}

3、唤醒事件唤醒

可以通过如下函数打印log查看唤醒源,

bool pm_wakeup_pending(void)
{
?? ?unsigned long flags;
?? ?bool ret = false;

?? ?spin_lock_irqsave(&events_lock, flags);
?? ?if (events_check_enabled) {
?? ??? ?unsigned int cnt, inpr;

?? ??? ?split_counters(&cnt, &inpr);
?? ??? ?ret = (cnt != saved_count || inpr > 0);
?? ??? ?events_check_enabled = !ret;
?? ?}
?? ?spin_unlock_irqrestore(&events_lock, flags);

?? ?if (ret) {
?? ??? ?pr_info("PM: Wakeup pending, aborting suspend\n");
?? ??? ?pm_print_active_wakeup_sources();
?? ?}

?? ?return ret || pm_abort_suspend;
}

文章来源:https://blog.csdn.net/weixin_44740132/article/details/135023583
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。