linux内核休眠、唤醒流程分析之挂起:pm.prepare(四)

本文深入探讨了Linux内核在设备挂起过程中的pm_prepare阶段,包括platform_suspend_begin函数的调用,控制台的挂起,以及dpm_suspend_start函数中设备的准备和挂起步骤。在设备挂起过程中,系统遍历活动设备列表,调用device_prepare进行设备的准备工作,关闭Runtime suspend功能,并根据设备的wakeup_path和callback函数进行相应处理。

在这里插入图片描述

然后是进入函数

// kernel\power\suspends.c

/**
 * suspend_devices_and_enter - Suspend devices and enter system sleep state.
 * @state: System sleep state to 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;

	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");
		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(state)中,判断平台代码中是否有begain函数,如果有就调用相关的begain函数。

// kernel\power\suspends.c

static int platform_suspend_begin(suspend_state_t state)
{
	if (state == PM_SUSPEND_TO_IDLE && s2idle_ops && s2idle_ops->begin)
		return s2idle_ops->begin();
	else if (suspend_ops && suspend_ops->begin)
		return suspend_ops->begin(state);
	else
		return 0;
}

然后调用suspend_console挂起控制台,此时printk()函数无法显示。

// kernel\printk\printk.c

/**
 * suspend_console - suspend the console subsystem
 *
 * This disables printk() while we go into suspend states
 */
void suspend_console(void)
{
	if (!console_suspend_enabled)
		return;
	pr_info("Suspending console(s) (use no_console_suspend to debug)\n");
	console_lock();
	console_suspended = 1;
	up_console_sem();
}

然后是进入到函数dpm_suspend_start(PMSG_SUSPEND)

// devices\base\power\main.c

/**
 * dpm_suspend_start - Prepare devices for PM transition and suspend them.
 * @state: PM transition of the system being carried out.
 *
 * Prepare all non-sysdev devices for system PM transition and execute "suspend"
 * callbacks for them.
 */
int dpm_suspend_start(pm_message_t state)
{
	ktime_t starttime = ktime_get();
	int error;

	error = dpm_prepare(state);
	if (error) {
		suspend_stats.failed_prepare++;
		dpm_save_failed_step(SUSPEND_PREPARE);
	} else
		error = dpm_suspend(state);
	dpm_show_time(starttime, state, error, "start");
	return error;
}
EXPORT_SYMBOL_GPL(dpm_suspend_start);

上边这个函数主要实现了电源管理的准备过程和设备的挂起过程。
通过error = dpm_prepare(state);实现了流程图中的过程3
通过error = dpm_suspend(state);实现了流程图中的过程4

// drivers\base\power\main.c

/**
 * dpm_prepare - Prepare all non-sysdev devices for a system PM transition.
 * @state: PM transition of the system being carried out.
 *
 * Execute the ->prepare() callback(s) for all devices.
 */
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;
			}
			pr_info("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_list为活动设备的列表。

// drivers\base\power\power.h

/* drivers/base/power/main.c */
extern struct list_head dpm_list;	/* The active device list */

这里是遍历所有活动设备的列表dpm_list,然后依次进入device_prepare(dev, state)函数,如果执行成功,将dev->power.is_prepared(就是struct dev_pm_info类型的变量)设为TRUE,表示设备已经prepared了。同时,将该设备添加到dpm_prepared_list中(该链表保存了所有已经处于prepared状态的设备)。
关于dpm_list,设备模型在添加设备(device_add)时,会调用device_pm_add接口,将该设备添加到全局链表dpm_list中,以方便后续的遍历操作。如下所示。

// driver\base\power\main.c

/**
 * device_pm_add - Add a device to the PM core's list of active devices.
 * @dev: Device to add to the list.
 */
void device_pm_add(struct device *dev)
{
	/* Skip PM setup/initialization. */
	if (device_pm_not_required(dev))
		return;

	pr_debug("Adding info for %s:%s\n",
		 dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
	device_pm_check_callbacks(dev);
	mutex_lock(&dpm_list_mtx);
	if (dev->parent && dev->parent->power.is_prepared)
		dev_warn(dev, "parent %s should not be sleeping\n",
			dev_name(dev->parent));
	list_add_tail(&dev->power.entry, &dpm_list);
	dev->power.in_dpm_list = true;
	mutex_unlock(&dpm_list_mtx);
}

调用device_prepare执行实际的prepare动作。

// drivers\base\power\main.c

/**
 * device_prepare - Prepare a device for system power transition.
 * @dev: Device to handle.
 * @state: PM transition of the system being carried out.
 *
 * Execute the ->prepare() callback(s) for given device.  No new children of the
 * device may be registered after this function has returned.
 */
static int device_prepare(struct device *dev, pm_message_t state)
{
	int (*callback)(struct device *) = NULL;
	int ret = 0;

	if (dev->power.syscore)
		return 0;

	/*
	 * If a device's parent goes into runtime suspend at the wrong time,
	 * it won't be possible to resume the device.  To prevent this we
	 * block runtime suspend here, during the prepare phase, and allow
	 * it again during the complete phase.
	 */
	pm_runtime_get_noresume(dev);

	device_lock(dev);

	dev->power.wakeup_path = false;

	if (dev->power.no_pm_callbacks)
		goto unlock;

	if (dev->pm_domain)
		callback = dev->pm_domain->ops.prepare;
	else if (dev->type && dev->type->pm)
		callback = dev->type->pm->prepare;
	else if (dev->class && dev->class->pm)
		callback = dev->class->pm->prepare;
	else if (dev->bus && dev->bus->pm)
		callback = dev->bus->pm->prepare;

	if (!callback && dev->driver && dev->driver->pm)
		callback = dev->driver->pm->prepare;

	if (callback)
		ret = callback(dev);

unlock:
	device_unlock(dev);

	if (ret < 0) {
		suspend_report_result(callback, ret);
		pm_runtime_put(dev);
		return ret;
	}
	/*
	 * A positive return value from ->prepare() means "this device appears
	 * to be runtime-suspended and its state is fine, so if it really is
	 * runtime-suspended, you can leave it in that state provided that you
	 * will do the same thing with all of its descendants".  This only
	 * applies to suspend transitions, however.
	 */
	spin_lock_irq(&dev->power.lock);
	dev->power.direct_complete = state.event == PM_EVENT_SUSPEND &&
		(ret > 0 || dev->power.no_pm_callbacks) &&
		!dev_pm_test_driver_flags(dev, DPM_FLAG_NO_DIRECT_COMPLETE);
	spin_unlock_irq(&dev->power.lock);
	return 0;
}

内部接口device_prepare的执行动作为:
1)根据dev->power.syscore,断该设备是否为syscore设备。如果是,则直接返回(因为syscore设备会单独处理)。
2)在prepare时期,调用pm_runtime_get_noresume接口,关闭Runtime suspend功能。以避免由Runtime suspend造成的不能正常唤醒的Issue。该功能会在complete时被重新开启。
【注:pm_runtime_get_noresume的实现很简单,就是增加该设备power变量的引用计数(dev->power.usage_count),Runtime PM会根据该计数是否大于零,判断是否开启Runtime PM功能。】
3)该设备默认不是一个wakeup path(记录在dev->power.wakeup_path中)。
【注:设备的wake up功能,是指系统在低功耗状态下(如suspend、hibernate),某些设备具备唤醒系统的功能。这是电源管理过程的一部分。】
4)根据优先顺序,获得用于prepare的callback函数。由于设备模型有bus、driver、device等多个层级,而prepare接口可能由任意一个层级实现。这里的优先顺序是指,只要优先级高的层级注册了prepare,就会优先使用它,而不会使用优先级低的prepare。优先顺序为:dev->pm_domain->ops、dev->type->pm、dev->class->pm、dev->bus->pm、dev->driver->pm(这个优先顺序同样适用于其它callbacks)。
5)如果得到有限的prepare函数,调用并返回结果。

参考资源链接:[Android休眠唤醒驱动详解](https://wenku.csdn.net/doc/12jh0tcao5?utm_source=wenku_answer2doc_content) 对于想要深入理解Android设备休眠唤醒流程的开发者来说,《Android休眠唤醒驱动详解》是一本不可多得的资源。它详细解析了这一过程中涉及的Linux内核电源管理机制和Android特有的服务框架管理。现在,让我们结合sys文件系统和Linux内核机制来探讨如何控制设备休眠唤醒流程。 首先,理解Linux内核中的休眠流程是关键。当系统进入休眠状态时,内核会通过一系列的步骤来确保数据安全和资源的最小化使用。具体来说,可以分为以下几个阶段: 1. **保存系统状态**:在休眠前,系统会保存必要的运行状态,确保在唤醒后能够恢复到休眠前的状态。 2. **通知内核准备休眠**:通过向`/sys/power/state`文件写入特定值(如'mem'表示挂起到内存),通知内核开始准备休眠。 3. **执行休眠**:内核根据写入的值执行相应的休眠流程,如挂起到内存、挂起到磁盘等。 在Linux内核中,休眠流程分为多个阶段,从PM_SUSPEND_PREPARE开始,直到系统进入睡眠状态。这一过程中,各种设备和CPU也会逐步被挂起以降低功耗。 唤醒过程则是休眠的逆过程: 1. **系统重启**:在CPU重启后,内核会执行从休眠状态恢复的代码。 2. **恢复设备**:设备驱动程序会逐步恢复设备状态,重新激活被冻结的任务,并恢复中断处理。 3. **通知内核休眠结束**:通过PM_POST_SUSPEND通知,系统会知道休眠过程已经结束,所有设备和服务都应该恢复到正常运行状态。 在Android系统中,这一过程同样涉及到Android特有的电源管理框架,它会协调窗口管理器、网络连接、传感器等组件的休眠唤醒,保证应用和系统状态的一致性。 通过结合`/sys/power/state`文件和Linux内核提供的机制,开发者能够更精确地控制Android设备的休眠唤醒流程,这对于优化电池寿命和系统性能具有重要的实际意义。对于想要深入学习如何通过编程方式控制这一过程的开发者,《Android休眠唤醒驱动详解》提供了详尽的指导和示例代码,是解决此类问题不可多得的辅助资料。 参考资源链接:[Android休眠唤醒驱动详解](https://wenku.csdn.net/doc/12jh0tcao5?utm_source=wenku_answer2doc_content)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值