Linux电源管理(6)_Generic PM之Suspend功能

1. 前言

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处理等过个知识点。就让我们跟着内核代码,一一见识它们吧。

2. Suspend功能有关的代码分布

内核中Suspend功能有关的代码包括PM core、Device PM、Platform PM等几大块,具体如下:

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/*----具体可参考“Linux电源管理(4)_Power Management Interface”的描述。

设备驱动----具体设备驱动的位置,不再涉及。

3)Platform dependent PM

include/linux/suspend.h----定义platform dependent PM有关的操作函数集

arch/xxx/mach-xxx/xxx.c或者

arch/xxx/plat-xxx/xxx.c----平台相关的电源管理操作

3. suspend&resume过程概述

下面图片对Linux suspend&resume过程做了一个概述,读者可以顺着这个流程阅读内核源代码。具体的说明,可以参考后面的代码分析。
在这里插入图片描述

4. 代码分析

4.1 suspend入口

在用户空间执行如下操作:

echo "freeze" > /sys/power/state

echo "standby" > /sys/power/state

echo "mem" > /sys/power/state

会通过sysfs触发suspend的执行,相应的处理代码如下:

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;

	// 获取 pm_autosleep 的锁
	error = pm_autosleep_lock();
	if (error)
		return error;

	// 检查是否支持挂起操作
	if (pm_autosleep_state() > PM_SUSPEND_ON) {
		// 如果不支持挂起,则返回 EBUSY 错误
		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 {
		// 挂起状态值无效,返回 EINVAL 错误
		error = -EINVAL;
	}

out:
	// 释放 pm_autosleep 的锁
	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_TO_IDLE	((__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_TO_IDLE
#define PM_SUSPEND_MAX		((__force suspend_state_t) 4)
PM_SUSPEND_ON: 表示系统处于挂起状态。它的值为 0PM_SUSPEND_TO_IDLE: 表示系统挂起至空闲状态。它的值为 1PM_SUSPEND_STANDBY: 表示系统挂起至待机状态。它的值为 2PM_SUSPEND_MEM: 表示系统挂起至内存状态。它的值为 3PM_SUSPEND_MIN: 表示最小的挂起状态,即 PM_SUSPEND_TO_IDLEPM_SUSPEND_MAX: 表示最大的挂起状态,它的值为 4

根据state的值,如果不是(PM_SUSPEND_MAX,对应hibernate功能),则调用pm_suspend接口,进行后续的处理。

pm_suspend在kernel/power/suspend.c定义,处理所有的suspend过程。

4.2 pm_suspend & enter_state

pm_suspend的实现非常简单,简单的做一下参数合法性判断,直接调用enter_state接口,如下:

/**
 * pm_suspend - Externally visible function for suspending the system.
 * @state: System sleep state to enter.
 *
 * Check if the value of @state represents one of the supported states,
 * execute enter_state() and update system suspend statistics.
 */
int pm_suspend(suspend_state_t state)
{
	int error;

	// 检查挂起状态是否有效,如果无效则返回错误
	if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
		return -EINVAL;

	// 打印挂起入口信息,使用 mem_sleep_labels 数组根据状态索引获取状态名称
	pr_info("suspend entry (%s)\n", mem_sleep_labels[state]);

	// 调用 enter_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;
}
EXPORT_SYMBOL(pm_suspend);

总之,pm_suspend() 函数用于处理系统的挂起操作。它会检查状态的有效性,执行相应的挂起操作,并更新系统的挂起统计信息。

enter_state代码为:

/**
 * enter_state - Do common work needed to enter system sleep state.
 * @state: System sleep state to enter.
 *
 * Make sure that no one else is trying to put the system into a sleep state.
 * Fail if that's not the case. Otherwise, prepare for system suspend, make the
 * system enter the given sleep state, and clean up after wakeup.
 */
static int enter_state(suspend_state_t state)
{
	int error;

	// 跟踪挂起和恢复事件,记录挂起入口的追踪信息
	trace_suspend_resume(TPS("suspend_enter"), state, true);

	// 如果挂起状态是 PM_SUSPEND_TO_IDLE,则检查测试级别是否支持
	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(&system_transition_mutex))
		return -EBUSY;

	// 如果挂起状态是 PM_SUSPEND_TO_IDLE,则调用 s2idle_begin() 准备进入 idle 状态
	if (state == PM_SUSPEND_TO_IDLE)
		s2idle_begin();

	// 如果启用了挂起时同步文件系统的选项,则调用 ksys_sync_helper() 同步文件系统
	if (sync_on_suspend_enabled) {
		trace_suspend_resume(TPS("sync_filesystems"), 0, true);
		ksys_sync_helper();
		trace_suspend_resume(TPS("sync_filesystems"), 0, false);
	}

	// 打印准备进入挂起状态的日志信息
	pm_pr_dbg("Preparing system for sleep (%s)\n", mem_sleep_labels[state]);

	// 清除挂起标志位
	pm_suspend_clear_flags();

	// 调用 suspend_prepare() 函数准备进入挂起状态
	error = suspend_prepare(state);
	if (error)
		goto Unlock;

	// 如果挂起测试标志位设置为 TEST_FREEZER,则跳过挂起操作
	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]);

	// 限制内存分配的 GFP 标志位,以便在挂起期间只能使用更保守的标志位
	pm_restrict_gfp_mask();

	// 调用 suspend_devices_and_enter() 函数挂起设备并进入挂起状态
	error = suspend_devices_and_enter(state);

	// 恢复内存分配的 GFP 标志位
	pm_restore_gfp_mask();

Finish:
	// 禁用事件检查
	events_check_enabled = false;

	// 打印挂起唤醒完成的日志信息
	pm_pr_dbg("Finishing wakeup.\n");

	// 执行挂起完成的清理操作
	suspend_finish();

Unlock:
	// 释放系统转换互斥锁
	mutex_unlock(&system_transition_mutex);

	return error;
}
这段代码的主要功能是执行进入系统睡眠状态所需的公共工作。具体分析如下:

函数接受一个 suspend_state_t 类型的参数 state,表示要进入的系统睡眠状态。
首先,使用 trace_suspend_resume() 跟踪挂起和恢复事件,并记录挂起入口的追踪信息。
如果挂起状态是 PM_SUSPEND_TO_IDLE,则检查测试级别是否支持。如果测试级别不支持挂起到空闲状态,则返回 -EAGAIN 错误码。
否则,检查挂起状态的有效性。如果状态无效,则返回 -EINVAL 错误码。
接着,尝试获取系统转换互斥锁,以确保没有其他操作正在尝试进入睡眠状态。如果获取锁失败,则返回 -EBUSY 错误码。
如果挂起状态是 PM_SUSPEND_TO_IDLE,则调用 s2idle_begin() 准备进入空闲状态。
如果启用了挂起时同步文件系统的选项,先使用 ksys_sync_helper() 同步文件系统。
打印准备进入挂起状态的日志信息。
清除挂起标志位。
调用 suspend_prepare() 函数准备进入挂起状态。该函数会根据挂起状态执行一些准备工作,如停止定时器、保存设备状态等。
如果挂起测试标志位设置为 TEST_FREEZER,则跳过挂起操作。
跟踪挂起和恢复事件,并记录挂起退出的追踪信息。
打印进入挂起状态的日志信息。
限制内存分配的 GFP(Get Free Page)标志位,以便在挂起期间只能使用更保守的标志位。
调用 suspend_devices_and_enter() 函数挂起设备并进入挂起状态。该函数会依次调用每个设备的 suspend() 方法,并将系统进入指定的挂起状态。
恢复内存分配的 GFP 标志位。
如果执行了挂起测试,则直接跳到 Finish 标签处,绕过一些后续操作。
禁用事件检查。
打印挂起唤醒完成的日志信息。
执行挂起完成的清理操作,如恢复定时器、恢复设备状态等。
释放系统转换互斥锁。
返回挂起操作的结果。

这段代码实现了进入系统睡眠状态的通用工作,包括准备系统、挂起设备、限制内存分配等。具体的操作在不同的挂起状态下可能会有所不同。代码中还考虑了一些特殊情况,如挂起测试模式、同步文件系统等。

主要工作包括:

  1. 调用valid_state,判断该平台是否支持该电源状态。
    suspend的最终目的,是让系统进入可恢复的挂起状态,而该功能必须有平台相关代码的参与才能完成,因此内核PM Core就提供了一系列的回调函数(封装在platform_suspend_ops中),让平台代码(如arch/arm/mach-xxx/pm.c)实现,然后由PM Core在合适的时机调用。这些回调函数包含一个valid函数,就是用来告知PM Core,支持哪些state。
static bool valid_state(suspend_state_t state)
{
	/*
	 * The PM_SUSPEND_STANDBY and PM_SUSPEND_MEM states require low-level
	 * support and need to be valid to the low-level implementation.
	 *
	 * No ->valid() or ->enter() callback implies that none are valid.
	 */
	return suspend_ops && suspend_ops->valid && suspend_ops->valid(state) &&
		suspend_ops->enter;
}

这段代码的作用是在运行时电源管理的过程中,验证给定的挂起状态是否有效。通过检查相关回调函数的存在与否,以及低层支持的要求,可以判断给定的挂起状态是否可以被处理。这个函数在系统挂起前的准备阶段被调用,以确保所选的挂起状态是可行的,并且相关的操作可以被执行。


[    0.000000][    T0] Call trace:
[    0.000000][    T0]  dump_backtrace+0xf4/0x11c
[    0.000000][    T0]  show_stack+0x18/0x24
[    0.000000][    T0]  dump_stack_lvl+0x60/0x90
[    0.000000][    T0]  suspend_set_ops+0x24/0x134
[    0.000000][    T0]  psci_init_system_suspend+0x5c/0x6c
[    0.000000][    T0]  psci_probe+0x108/0x158
[    0.000000][    T0]  psci_1_0_init+0x24/0x54
[    0.000000][    T0]  psci_dt_init+0x70/0xa8
[    0.000000][    T0]  setup_arch+0x170/0x238
[    0.000000][    T0]  start_kernel+0x7c/0x4f4
[    0.000000][    T0]  __primary_switched+0xc4/0x7bbc

suspend_ops回调函数调用的流程。
  1. 调用suspend_prepare,进行suspend前的准备,主要包括switch console和process&thread freezing。如果失败,则终止suspend过程。
  2. 然后,调用suspend_devices_and_enter接口,该接口负责suspend和resume的所有实际动作。前半部分,suspend console、suspend device、关中断、调用平台相关的suspend_ops使系统进入低功耗状态。后半部分,在系统被事件唤醒后,处理相关动作,调用平台相关的suspend_ops恢复系统、开中断、resume device、resume console
  3. 最后,调用suspend_finish,恢复(或等待恢复)process&thread,还原console。

4.3 suspend_prepare

/**
 * suspend_prepare - Prepare for entering system sleep state.
 * @state: Target system sleep state.
 *  * Common code run for every system sleep state that can be entered (except for
 * hibernation).  Run suspend notifiers, allocate the "suspend" console and
 * freeze processes.
 */
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_robust(PM_SUSPEND_PREPARE, PM_POST_SUSPEND);
	if (error)
		goto Restore;

	trace_suspend_resume(TPS("freeze_processes"), 0, true);
	error = suspend_freeze_processes();
	trace_suspend_resume(TPS("freeze_processes"), 0, false);
	if (!error)
		return 0;

	log_suspend_abort_reason("One or more tasks refusing to freeze");
	suspend_stats.failed_freeze++;
	dpm_save_failed_step(SUSPEND_FREEZE);
	pm_notifier_call_chain(PM_POST_SUSPEND);
 Restore:
	pm_restore_console();
	return error;
}
  • 该函数以目标系统休眠状态(state)作为参数。
  • 首先,它通过调用sleep_state_supported函数检查给定的休眠状态是否受系统支持。如果不支持,则返回错误码EPERM,表示权限错误。
  • 接下来,它分配“suspend”控制台,用于系统的休眠和恢复操作。这确保在休眠/恢复期间可以捕获控制台输出。
  • 函数通过调用pm_notifier_call_chain_robust来运行休眠通知器。休眠通知器是由各个模块/驱动程序注册的回调函数,用于通知系统休眠操作的发生。在进入休眠状态之前触发PM_SUSPEND_PREPARE事件,在系统恢复后触发PM_POST_SUSPEND事件。
  • 如果在运行休眠通知器链时出现错误,它跳转到Restore标签处,并在返回错误之前恢复控制台状态。
  • 如果休眠通知器顺利执行,函数接着调用suspend_freeze_processes冻结进程,即暂停进程的执行,以确保它们不会影响系统休眠的进行。
  • 在冻结进程期间,通过调用trace_suspend_resume函数记录冻结进程的跟踪信息。
  • 如果所有进程成功冻结,函数返回0,表示准备进入休眠状态的操作成功。
  • 如果冻结进程时发生错误,函数记录失败原因,包括一些任务拒绝冻结,并更新相关的统计信息。
  • 最后,函数调用pm_notifier_call_chain触发PM_POST_SUSPEND事件,通知已注册的模块/驱动程序系统休眠操作的完成。
  • 在函数结尾处,它恢复控制台状态并返回错误码(如果有)。

该函数的目的是在进入系统休眠状态之前执行一些通用的准备工作,包括运行休眠通知器、分配控制台和冻结进程。它确保系统处于适当的状态,以便顺利进入休眠状态,并处理可能出现的错误情况。

4.4 suspend_devices_and_enter

/**
 * suspend_devices_and_enter - 挂起设备并进入系统休眠状态
 * @state: 要进入的系统休眠状态
 */
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为目标休眠状态
	pm_suspend_target_state = state;

	// 如果目标状态为PM_SUSPEND_TO_IDLE,则设置挂起时不执行平台操作
	if (state == PM_SUSPEND_TO_IDLE)
		pm_set_suspend_no_platform();

	// 执行平台特定的挂起前准备操作
	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");
	// 如果挂起测试中指定了TEST_DEVICES,则跳转到Recover_platform标签处
	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
	pm_suspend_target_state = PM_SUSPEND_ON;
	return error;

Recover_platform:
	// 恢复平台特定的挂起前准备操作
	platform_recover(state);
	// 跳转到Resume_devices标签处,执行设备电源管理层次的恢复操作
	goto Resume_devices;
}

该函数的作用是在挂起设备之前进行准备工作,并在挂起状态退出后执行相应的恢复操作。它涵盖了挂起和恢复的各个阶段,包括准备设备、执行挂起操作、处理错误情况以及执行设备的恢复操作。

static int platform_suspend_begin(suspend_state_t state)
{
	// 如果目标状态是PM_SUSPEND_TO_IDLE,并且s2idle_ops和s2idle_ops->begin回调函数存在,则调用s2idle_ops->begin函数
	if (state == PM_SUSPEND_TO_IDLE && s2idle_ops && s2idle_ops->begin)
		return s2idle_ops->begin();
	// 否则,如果suspend_ops和suspend_ops->begin回调函数存在,则调用suspend_ops->begin函数
	else if (suspend_ops && suspend_ops->begin)
		return suspend_ops->begin(state);
	// 如果上述条件都不满足,则返回0
	else
		return 0;
}

/**
 * dpm_suspend_start - 准备设备进行电源管理转换并将其挂起
 * @state: 正在进行的系统电源管理转换
 *
 * 准备所有非系统设备进行系统电源管理转换,并执行相应的"挂起"回调函数。
 */
int dpm_suspend_start(pm_message_t state)
{
	ktime_t starttime = ktime_get();
	int error;

	// 调用 dpm_prepare 函数准备设备进行电源管理转换
	error = dpm_prepare(state);
	if (error) {
		suspend_stats.failed_prepare++;
		dpm_save_failed_step(SUSPEND_PREPARE);
	} else {
		// 调用 dpm_suspend 函数挂起设备
		error = dpm_suspend(state);
	}

	// 显示挂起操作的时间和结果
	dpm_show_time(starttime, state, error, "start");

	return error;
}
EXPORT_SYMBOL_GPL(dpm_suspend_start);

/**
 * suspend_enter - Make the system enter the given sleep state.
 * @state: System sleep state to enter.
 * @wakeup: Returns information that the sleep state should not be re-entered.
 *
 * This function should be called after devices have been suspended.
 */
static int suspend_enter(suspend_state_t state, bool *wakeup)
{
	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("late suspend of %s device failed",
					 suspend_stats.failed_devs[last_dev]);
		goto Platform_finish;
	}

	// 准备平台相关的延迟挂起操作
	error = platform_suspend_prepare_late(state);
	if (error)
		goto Devices_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;

	// 如果设置了 TEST_PLATFORM 标志,则直接唤醒系统并跳过挂起过程
	if (suspend_test(TEST_PLATFORM))
		goto Platform_wake;

	// 如果挂起状态是 PM_SUSPEND_TO_IDLE,则进入 s2idle 循环
	if (state == PM_SUSPEND_TO_IDLE) {
		s2idle_loop();
		goto Platform_wake;
	}

	// 禁用辅助 CPU 并进行相关测试
	error = pm_sleep_disable_secondary_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());

	// 设置系统状态为 SYSTEM_SUSPEND
	system_state = SYSTEM_SUSPEND;

	// 调用系统核心挂起操作
	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) {
			error = -EBUSY;
		}
		// 恢复系统核心
		syscore_resume();
	}

	// 恢复系统状态为 SYSTEM_RUNNING
	system_state = SYSTEM_RUNNING;

	// 启用中断
	arch_suspend_enable_irqs();
	BUG_ON(irqs_disabled());

 Enable_cpus:
	// 启用辅助 CPU
	pm_sleep_enable_secondary_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() 函数是用于将系统进入指定的睡眠状态的函数。以下是对该函数的总结分析:

函数开始执行时,先进行平台相关的挂起准备操作,以确保系统处于合适的状态进入睡眠状态。

接下来,执行延迟设备挂起操作,将系统中的设备逐个挂起。如果延迟挂起操作失败,则记录错误信息并跳转到平台恢复操作,终止进入睡眠状态的流程。

然后,执行平台相关的延迟挂起准备操作,为设备的延迟挂起做准备。

进行设备挂起操作(无中断),将设备进一步挂起。如果挂起操作失败,则记录错误信息并跳转到早期设备恢复操作,终止进入睡眠状态的流程。

执行平台相关的无中断挂起准备操作,为设备的无中断挂起做准备。

如果设置了 TEST_PLATFORM 标志,表示需要测试平台恢复,此时直接唤醒系统并跳过挂起过程。

如果挂起状态是 PM_SUSPEND_TO_IDLE,表示进入了空闲挂起状态,进入 s2idle 循环,直到有唤醒事件发生。

禁用辅助 CPU 并进行相关测试,确保非引导 CPU 处于禁用状态。

禁用中断,保证在挂起过程中不会发生中断。

设置系统状态为 SYSTEM_SUSPEND,表示系统正在挂起状态。

执行系统核心挂起操作,包括调用 syscore_suspend() 函数和具体的挂起操作函数。在此期间,会检查是否存在挂起唤醒请求,若无请求且挂起操作成功,则正常进入睡眠状态;若有挂起唤醒请求,则返回忙碌状态。

恢复系统核心,包括调用 syscore_resume() 函数。

恢复系统状态为 SYSTEM_RUNNING,表示系统已经恢复运行状态。

启用中断,允许中断再次发生。

启用辅助 CPU,确保非引导 CPU 处于启用状态。

执行无中断唤醒平台相关操作,准备系统唤醒过程。

执行早期设备恢复平台相关操作,将设备恢复到正常工作状态。

执行早期设备恢复操作,恢复设备的功能。

执行平台恢复操作,完成整个系统的恢复过程。

最后,函数返回挂起过程中可能出现的错误。

通过这个函数,系统可以根据指定的睡眠状态进入相应的挂起过程,并在恢复时逆向执行相应的操作,以确保设备和系统的正确恢复和运行。

/**
 * dpm_suspend_noirq - Execute "noirq suspend" callbacks for all devices.
 * @state: PM transition of the system being carried out.
 *
 * Prevent device drivers' interrupt handlers from being called and invoke
 * "noirq" suspend callbacks for all non-sysdev devices.
 */
int dpm_suspend_noirq(pm_message_t state)
{
	int ret;

	// 使设备唤醒并启用唤醒中断
	device_wakeup_arm_wake_irqs();

	// 暂停设备的中断处理程序
	suspend_device_irqs();

	// 调用所有非系统设备的"noirq"挂起回调函数
	ret = dpm_noirq_suspend_devices(state);

	// 如果挂起失败,则恢复设备的中断处理程序
	if (ret)
		dpm_resume_noirq(resume_event(state));

	return ret;
}

该函数用于执行所有设备的 “noirq suspend” 回调函数。以下是对函数的详细分析:

首先,调用 device_wakeup_arm_wake_irqs() 函数使设备唤醒并启用唤醒中断。这是为了确保设备在进入挂起状态后能够响应唤醒事件。

然后,调用 suspend_device_irqs() 函数暂停设备的中断处理程序。这是为了阻止设备驱动程序的中断处理程序在挂起过程中被调用,以确保设备在挂起期间不会产生中断。

接下来,调用 dpm_noirq_suspend_devices() 函数,它会遍历所有非系统设备,并调用它们的 “noirq suspend” 回调函数。这些回调函数是设备驱动程序提供的,在设备进入挂起状态之前执行必要的操作。

如果挂起过程中出现错误,函数会调用 dpm_resume_noirq() 函数,恢复设备的中断处理程序,以确保设备的正常操作。

最后,函数返回挂起过程中可能出现的错误代码。

总体来说,该函数用于在挂起过程中控制设备的中断处理程序,并执行设备驱动程序提供的回调函数,以便在设备进入挂起状态之前完成必要的清理和准备工作。这有助于确保设备在挂起和恢复过程中的正确操作。

[   80.195517][ T1831] Call trace:
[   80.198750][ T1831]  dump_backtrace+0x104/0x12c
[   80.203388][ T1831]  show_stack+0x20/0x30
[   80.207500][ T1831]  dump_stack_lvl+0x6c/0xa0
[   80.211957][ T1831]  genpd_suspend_noirq+0x28/0x4c
[   80.216850][ T1831]  dpm_run_callback+0x6c/0x26c
[   80.221571][ T1831]  __device_suspend_noirq+0x1c8/0x2b8
[   80.226899][ T1831]  dpm_suspend_noirq+0x110/0x50c
[   80.231792][ T1831]  suspend_devices_and_enter+0x214/0xb18
[   80.237378][ T1831]  pm_suspend+0x33c/0x6bc
[   80.241661][ T1831]  state_store+0x10c/0x14c

4.4 suspend_finish

/**
 * suspend_finish - Clean up before finishing the suspend sequence.
 *
 * Call platform code to clean up, restart processes, and free the console that
 * we've allocated. This routine is not called for hibernation.
 */
static void suspend_finish(void)
{
	// 恢复挂起前被冻结的进程
	suspend_thaw_processes();

	// 调用所有注册的后挂起通知器
	pm_notifier_call_chain(PM_POST_SUSPEND);

	// 恢复控制台设置
	pm_restore_console();
}

该函数用于在挂起序列结束之前进行清理操作。以下是对函数的详细分析:

首先,调用 suspend_thaw_processes() 函数来恢复之前被冻结的进程。在挂起过程中,为了确保系统状态的一致性,一些进程可能会被暂停运行,而在恢复过程中需要重新启动它们。

然后,调用 pm_notifier_call_chain(PM_POST_SUSPEND) 函数,它会调用所有已注册的后挂起通知器。这些通知器允许设备驱动程序和其他系统组件在挂起完成后执行特定的操作,如重新初始化设备、重新启动服务等。

最后,调用 pm_restore_console() 函数来恢复控制台设置。在挂起过程中,为了保持挂起过程的可见性,可能会分配和修改控制台资源,而在挂起完成后需要将其恢复为原始设置。

总体来说,该函数用于在挂起序列的最后阶段进行清理操作,包括恢复被冻结的进程、调用后挂起通知器以及恢复控制台设置。这些步骤有助于确保系统在挂起过程完成后能够正常运行,并恢复到正常的工作状态。

5. device PM ops 和platform PM ops的调用时机

在这里插入图片描述

参考链接:http://www.wowotech.net/pm_subsystem/suspend_and_resume.html

<think>好的,用户问的是如何让i.MX8M芯片中的M核唤醒A核,并且需要A核完全不工作的情况下该怎么做。首先,我需要理解用户的具体需求。用户可能是在设计一个低功耗系统,希望大部分时间由M核处理简单任务,A核完全关闭以节省功耗,当需要时再由M核唤醒A核。 首先,我得回顾一下i.MX8M的架构。i.MX8M通常有Cortex-A53(A核)和Cortex-M4(M核)。A核运行Linux,而M核运行实时操作系统或裸机程序。在默认情况下,A核可能处于低功耗状态,比如挂起到内存(Suspend-to-RAM),但用户希望A核完全关闭,这可能涉及到更深层次的电源状态,比如电源门控。 接下来,我需要考虑如何让A核完全关闭。通常,在Linux中,挂起到RAM(mem)状态会让A核进入低功耗模式,但可能没有完全断电。要实现完全关闭,可能需要进入更深层次的电源模式,比如电源关断(Power Down),这可能需要特定的硬件支持和配置。 然后,唤醒机制。如果A核完全关闭,M核需要通过某种方式触发A核的重新上电和启动。这可能需要配置PMIC(电源管理IC)或者使用硬件信号,比如复位引脚或特定的唤醒源。需要检查i.MX8M的文档,看是否有这样的功能,比如通过GPC(通用电源控制器)模块或SCU(系统控制单元)来管理核的上下电。 接下来是具体步骤。首先,需要配置A核进入关闭状态。这可能涉及到Linux内核的电源管理配置,但常规的suspend可能不够,需要定制化的操作,比如通过U-Boot设置或者在Linux中调用特定接口。然后,M核需要能够发送唤醒信号,可能需要配置中断或GPIO,触发PMIC重新供电给A核,或者通过硬件复位。 另外,需要考虑外设的状态。如果A核完全关闭,其管理的外设可能需要由M核接管,或者确保这些外设在A核关闭时处于安全状态。例如,DDR内存可能需要保持自刷新模式,或者完全断电,这取决于系统设计。 还需要验证整个流程的可行性。是否有现有的案例或文档支持这种操作?i.MX8M的参考手册中是否有关于核电源管理的详细说明?可能需要查阅相关文档,确认硬件支持的功能,例如是否存在电源门控的选项,以及如何通过软件控制这些电源域。 此外,安全性也是一个考虑因素。强制关闭A核是否会影响系统的稳定性?唤醒后的启动流程是否需要重新初始化硬件,比如时钟、内存控制器等?这些步骤可能需要仔细的配置,避免系统崩溃或数据丢失。 最后,调试和测试。如何验证A核确实完全关闭?可以使用电流测量或调试工具监控电源状态。唤醒过程中可能会遇到问题,比如A核无法正确启动,需要检查唤醒源的配置,电源恢复的时序,以及启动代码的正确性。 总结一下,用户的需求是实现A核完全关闭并由M核唤醒,这需要深入硬件电源管理配置,可能的步骤包括配置A核进入深度电源关断状态,设置M核控制的唤醒源,管理外设和电源域,以及确保唤醒后的正确初始化。需要参考具体的芯片文档,确认支持的功能,并进行详细的配置和测试。</think>--- ### **基于i.MX8M的M核唤醒A核实现:强制关闭A核的配置方法** 在i.MX8M系列芯片中,若需**完全关闭A核**(如Cortex-A53)并仅通过M核(Cortex-M4)唤醒,需结合硬件电源域控制与系统级协同设计。以下是具体实现方案: --- ### **1. A核完全关闭的硬件要求** i.MX8M的A核电源域支持**动态电源关断(Power Gating)**,需满足以下条件: - **电源管理芯片(PMIC)**支持对A核电源域的独立控制。 - **GPC(Generic Power Controller)**模块配置为隔离A核电源。 - **M核与A核间通信机制**(如RPMsg、MU模块)用于唤醒信号传递。 --- ### **2. 实现步骤** #### **步骤1:强制关闭A核电源** 关闭A核的电源域需通过GPC和PMIC协作完成: 1. **Linux端配置(A核运行期间)**: - 在A核运行的Linux中,通过内核命令或用户空间工具触发深度休眠模式(如`Power-Off`模式),而非常规的`Suspend-to-RAM`。 - 示例命令(需内核支持): ```bash # 触发深度电源关断(需自定义PM驱动) echo "poweroff" > /sys/power/state ``` - **关键内核配置**: ```bash CONFIG_PM_DEBUG=y CONFIG_IMX8M_PM_DOMAINS=y ``` 2. **硬件级关断(PMIC操作)**: - 通过PMIC关闭A核的供电域(如VDD_ARM)。 - 需在PMIC驱动中实现电源域控制逻辑,例如: ```c // PMIC驱动示例(关闭A核电源) pmic_reg_write(PMIC_VDD_ARM_ADDR, 0x00); // 写入关断命令 ``` --- #### **步骤2:M核唤醒A核的机制** M核通过硬件信号或中断唤醒A核的两种方式: ##### **方式1:通过PMIC触发唤醒** - **M核发送唤醒信号**: M核通过I2C/SPI向PMIC发送命令,重新开启A核电源域(如VDD_ARM)。 ```c // M核代码(通过I2C控制PMIC) i2c_write(PMIC_ADDR, PMIC_VDD_ARM_ADDR, 0x01); // 恢复A核供电 ``` - **PMIC响应**: PMIC重新上电后,触发A核复位引脚(POR_B),A核从BootROM重新启动。 ##### **方式2:直接使用硬件复位信号** - **M核控制复位引脚**: M核通过GPIO拉低A核的复位信号(如`A53_RESET_B`),强制A核复位重启。 ```c // M核配置GPIO为输出模式并触发复位 GPIO_SetDirection(A53_RESET_GPIO, OUTPUT); GPIO_WritePin(A53_RESET_GPIO, LOW); // 拉低复位信号 delay(10); // 保持低电平至少1个时钟周期 GPIO_WritePin(A53_RESET_GPIO, HIGH); // 释放复位信号 ``` --- ### **3. 关键配置与验证** #### **3.1 设备树隔离A核外设** 在设备树中禁用A核独占外设,避免唤醒后资源冲突: ```dts // 示例:禁用A核管理的PCIe控制器 &pcie0 { status = "disabled"; // 关闭PCIe,由M核管理其他外设 }; ``` #### **3.2 唤醒后A核的启动流程** - **冷启动**: A核重启后从BootROM重新加载U-Boot和内核,需确保: - Boot介质(如eMMC)的供电在A核关闭期间保持有效。 - DDR重新初始化(若DDR在A核关闭时断电)。 - **快速恢复**: 若需保留A核状态,需在关闭前将关键数据保存到非易失存储(如OCRAM或PMIC SRAM)。 --- ### **4. 调试与风险控制** #### **4.1 风险点** - **电源时序错误**: PMIC上电顺序错误可能导致A核无法启动。需严格遵循i.MX8M电源轨时序要求。 - **外设冲突**: A核关闭后,其管理的外设(如DDR、GPU)需由M核接管或保持断电状态。 #### **4.2 调试方法** - **测量电源域状态**: 使用万用表或电流探头验证VDD_ARM是否完全关闭。 - **M核日志输出**: 通过UART或SWD接口输出M核调试信息,确认唤醒信号已发送。 - **A核启动跟踪**: 通过JTAG监控A核复位后的BootROM执行流程。 --- ### **5. 典型应用场景** - **超低功耗设备**: A核仅在需要复杂计算时唤醒(如图像处理),其余时间由M核处理传感器数据。 - **工业控制器**: M核实时响应中断,A核负责周期性任务(如日志上传)。 --- ### **6. 总结** - **核心操作**: 1. **关闭A核电源**:通过PMIC或GPC模块切断供电。 2. **M核触发唤醒**:通过PMIC命令或硬件复位信号重启A核。 - **注意事项**: - 验证电源时序与复位信号稳定性。 - 隔离A核外设以避免硬件冲突。 - 若需快速恢复,需设计状态保存机制。 若需进一步优化功耗或实现状态热迁移,可结合**动态电压频率调整(DVFS)**或**TCM内存保留**技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值