五、suspend和resume代码走读
下面对suspend分的几个阶段都是按照pm test的5中模式来划分的:freezer、devices、platform、processors、core。
suspend第一阶段:freezer
int enter_state(suspend_state_t state)
{
int error;
if (!valid_state(state))
return -ENODEV;
if (!mutex_trylock(&pm_mutex)) // def in kernel/kernel/power/main.c
return -EBUSY;
printk(KERN_INFO "PM: Syncing filesystems ... ");
sys_sync();
printk("done./n"); // 同步文件系统
pr_debug("PM: Preparing system for %s sleep/n", pm_states[state]);
error = suspend_prepare();
// suspend前准备工作:Run suspend notifiers, allocate a console and stop all processes
if (error) // 如果一些准备工作失败,通常为冻结进程的时候某些进程拒绝进入冻结模式
goto Unlock; // 释放锁,然后退出
if (suspend_test(TEST_FREEZER))
// 检查上层下达的命令是否是:
// echo freezer > /sys/power/pm_test
// echo mem > /sys/power/state
// 是的话,延时5s后,然后做一些解冻进程等工作就返回
goto Finish;
pr_debug("PM: Entering %s sleep/n", pm_states[state]);
error = suspend_devices_and_enter(state); // 休眠外设
Finish:
pr_debug("PM: Finishing wakeup./n");
suspend_finish(); // 解冻进程,发广播通知等
Unlock:
mutex_unlock(&pm_mutex);
return error;
}
static int suspend_prepare(void)
{
int error;
if (!suspend_ops || !suspend_ops->enter) // mtk_pm_enter() in mtkpm.c
return -EPERM;
pm_prepare_console(); //给suspend分配一个虚拟终端来输出信息
error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
// 广播一个通知给通知链pm_chain_head,该通知链上都是用函数register_pm_notifier()注册的pm通知项(也可以用宏pm_notifier定义和注册),这里按照注册时候的定义的优先级来调用该通知链上注册的回调函数。
// PM_SUSPEND_PREPARE是事件值,表示将要进入suspend
if (error)
goto Finish;
error = usermodehelper_disable(); // 关闭用户态的helper进程
if (error)
goto Finish;
error = suspend_freeze_processes();
// 冻结所有的进程, 这里会保存所有进程当前的状态。
if (!error)
return 0;
// 也许有一些进程会拒绝进入冻结状态, 当有这样的进程存在的时候, 会导致冻结失败,此函数就会放弃冻结进程,并且解冻刚才冻结的所有进程.
suspend_thaw_processes(); // 进程解冻
usermodehelper_enable(); // enable helper process
Finish:
pm_notifier_call_chain(PM_POST_SUSPEND); // 广播退出suspend的通知
pm_restore_console();
return error;
}
// 如果不支持pm debug的话,该函数直接返回0
static int suspend_test(int level)
{
#ifdef CONFIG_PM_DEBUG
if (pm_test_level == level) {
printk(KERN_INFO "suspend debug: Waiting for 5 seconds./n");
mdelay(5000);
return 1;
}
#endif /* !CONFIG_PM_DEBUG */
return 0;
}
static void suspend_finish(void)
{
suspend_thaw_processes();
usermodehelper_enable();
pm_notifier_call_chain(PM_POST_SUSPEND);
pm_restore_console();
}
同步文件系统函数sys_sync()调用后,将会执行函数suspend_prepare()来做一些进入suspend之前的准备工作:Run suspend notifiers, allocate a console and stop all processes,disable user mode hleper process。之后将会调用suspend_test(TEST_FREEZER)来判断上层是否有下这样的命令下来:echo freezer > /sys/power/pm_test(如果pm debug支持),如果没有下这个命令或者系统根本就不支持pm debug,那么直接返回0。如果该测试函数返回了1,那么就不会继续往下走suspend的流程了,而是调用函数suspend_finish()来结束freezer模式的pm test。返回0,将会进入第二阶段继续suspend的流程。
suspend第二阶段:devices
后续所有对suspend划分的阶段都包含在函数suspend_devices_and_enter(state)之中,所以这个函数是关键所在。
int suspend_devices_and_enter(suspend_state_t state)
{
int error;
if (!suspend_ops)
// 一个重要的函数指针结构体,特定平台的不一样,
// kernel/arch/arm/mach-mt6516/mtkpm.c
return -ENOSYS;
if (suspend_ops->begin) {
error = suspend_ops->begin(state);
// 调用特定平台实现的suspend_begin函数,
// 这里没做什么实际的工作,打印了点字符串
if (error)
goto Close; // 如果有错,执行特定平台的suspend_end函数
}
suspend_console(); // suspend console subsystem
suspend_test_start(); // suspend devices超时警告测试
error = dpm_suspend_start(PMSG_SUSPEND); // suspend all devices, 外设休眠
// 关键函数之一, kernel/drivers/base/power/main.c
if (error) {
printk(KERN_ERR "PM: Some devices failed to suspend/n");
goto Recover_platform; // 如果外设休眠过程出现错误,将会终止suspend过程,直接跳到某标签出resume外设
}
suspend_test_finish("suspend devices");
if (suspend_test(TEST_DEVICES)) // suspend第二阶段以此为界
goto Recover_platform;
suspend_enter(state); // 关键函数之一, pm test的后三种模式都在该函数中进行测试:platform、cpus、core
Resume_devices:
suspend_test_start();
dpm_resume_end(PMSG_RESUME);
suspend_test_finish("resume devices");
resume_console();
Close:
if (suspend_ops->end)
suspend_ops->end();
return error;
Recover_platform:
if (suspend_ops->recover)
suspend_ops->recover(); // 该函数目前的平台没有实现
goto Resume_devices;
}
@kernel/drivers/base/power/main.c
int dpm_suspend_start(pm_message_t state)
{
int error;
might_sleep();
error = dpm_prepare(state);
if (!error)
error = dpm_suspend(state); // 这两个函数的架构有一些类似
return error;
这两个函数如果在执行某一个device的->prepare()和->suspend回调函数的时候出错,都将会直接跳出返回错误码,不理会后续devices。
}
static int dpm_prepare(pm_message_t state)
{
struct list_head list;
int error = 0;
INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx); // 锁住dpm_list链表
transition_started = true; // device_pm_add()中使用
while (!list_empty(&dpm_list)) { // dpm_list上挂着所有的devices
// 关于device中的这部分内容,参考文档:
// 新版linux系统设备架构中关于电源管理方式的变更.txt
struct device *dev = to_device(dpm_list.next); // 取得对应的device结构体
get_device(dev);
dev->power.status = DPM_PREPARING;
// 将该设备的电源状态设置成DPM_PREPARING,表示该设备正准备着进入相应的省电模式,这之后就会调用和该设备有关的所以的-->prepare函数
mutex_unlock(&dpm_list_mtx);
pm_runtime_get_noresume(dev);
// 电源管理新方式中比较复杂的用法,这里没有使用到,所以直接跳过
if (pm_runtime_barrier(dev) && device_may_wakeup(dev)) {
/* Wake-up requested during system sleep transition. */
pm_runtime_put_noidle(dev);
error = -EBUSY;
} else {
error = device_prepare(dev, state);
// 这个才是真正执行-->prepare回调函数的地方
}
mutex_lock(&dpm_list_mtx);
if (error) {
dev->power.status = DPM_ON;
if (error == -EAGAIN) { // try again
put_device(dev);
error = 0;
continue;
}
printk(KERN_ERR "PM: Failed to prepare device %s "
"for power transition: error %d/n",
kobject_name(&dev->kobj), error);
put_device(dev);
break;
}
dev->power.status = DPM_SUSPENDING;
// 这之后就不能以它为父设备再注册设备了,可以从函数device_pm_add
// 中看出来
if (!list_empty(&dev->power.entry))
list_move_tail(&dev->power.entry, &list);
// 为了该函数中循环链表之用, list_empty(&dpm_list)
put_device(dev);
}
list_splice(&list, &dpm_list);
mutex_unlock(&dpm_list_mtx);
return error;
}
从这个函数我们可以发现,每一个device的dev->power.status状态都会有如下轨迹的变化:DPM_ON(device刚刚注册完的初始状态) --> DPM_PREPARING --> DPM_SUSPENDING --> 未完待续...
static int device_prepare(struct device *dev, pm_message_t state)
{
int error = 0;
down(&dev->sem);
// dev->bus绝大多数都存在,dev->bus->pm这个就不一定了,不过platform bus一定存在,而i2c bus就没有,dev->bus->pm->prepare这个函数如果存在就会被后面给调用到,以platform bus为例,该函数的实现完全回去调用device对应的driver->pm->prepare()函数(如果存在的话),当然driver->pm->prepare()不存在就什么也不做了。
if (dev->bus && dev->bus->pm && dev->bus->pm->prepare) {
pm_dev_dbg(dev, state, "preparing ");
error = dev->bus->pm->prepare(dev);
suspend_report_result(dev->bus->pm->prepare, error);
if (error)
goto End;
}
// dev->type这个很多设备都有,但是dev->type->pm这个却很少有了
if (dev->type && dev->type->pm && dev->type->pm->prepare) {
pm_dev_dbg(dev, state, "preparing type ");
error = dev->type->pm->prepare(dev);
suspend_report_result(dev->type->pm->prepare, error);
if (error)
goto End;
}
// dev->class很少有设备有
if (dev->class && dev->class->pm && dev->class->pm->prepare) {
pm_dev_dbg(dev, state, "preparing class ");
error = dev->class->pm->prepare(dev);
suspend_report_result(dev->class->pm->prepare, error);
}
End:
up(&dev->sem);
return error;
}
static int dpm_suspend(pm_message_t state)
{
struct list_head list;
int error = 0;
INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx);
while (!list_empty(&dpm_list)) {
struct device *dev = to_device(dpm_list.prev);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
dpm_drv_wdset(dev);
error = device_suspend(dev, state);
dpm_drv_wdclr(dev);
mutex_lock(&dpm_list_mtx);
if (error) {
pm_dev_err(dev, state, "", error);
put_device(dev);
break;
// 如果某个dev的suspend出错了,那么将不会执行后续dev的suspend函数,将会直接返回错误码给上级函数。
}
dev->power.status = DPM_OFF;
if (!list_empty(&dev->power.entry))
list_move(&dev->power.entry, &list);
put_device(dev);
}
device_suspend_index = 0; // device_suspend()函数中使用
list_splice(&list, dpm_list.prev);
mutex_unlock(&dpm_list_mtx);
return error;
}
前面执行了->prepare回调函数之后,dev->power.status状态已经为DPM_SUSPENDING了,那么在这里执行了->suspend回调函数后,其状态正常情况下将会变成DPM_OFF --> 未完待续...
static int device_suspend(struct device *dev, pm_message_t state)
{
int error = 0;
static int index = 0;
struct platform_device *pdev = to_platform_device(dev);
down(&dev->sem);
if (dev->class) {
if (dev->class->pm) { // 新式的电源管理方案: dev_pm_ops
pm_dev_dbg(dev, state, "class ");
if (pdev->name)
printk("[%d][0x%x] class
device_suspend/n/r",device_suspend_index, pdev->name);
else
printk("[%d] class device_suspend/n/r",device_suspend_index);
error = pm_op(dev, dev->class->pm, state);
// 调用回调函数dev->class->pm->suspend()
if (pdev->name)
printk("[%d][0x%x] class device_suspend
pass/n/r",device_suspend_index++, pdev->name);
else
printk("[%d] class device_suspend
pass/n/r",device_suspend_index++);
} else if (dev->class->suspend) { // 旧式的电源管理方案
pm_dev_dbg(dev, state, "legacy class ");
if (pdev->name)
printk("[%d][0x%x] legacy class
device_suspend/n/r",device_suspend_index, pdev->name);
else
printk("[%d] legacy class
device_suspend/n/r",device_suspend_index);
error = dev->class->suspend(dev, state);
if (pdev->name)
printk("[%d][0x%x] legacy class device_suspend
pass/n/r",device_suspend_index++, pdev->name);
else
printk("[%d] legacy class device_suspend
pass/n/r",device_suspend_index++);
suspend_report_result(dev->class->suspend, error); // 错误处理
}
if (error)
goto End;
}
if (dev->type) {
if (dev->type->pm) {
pm_dev_dbg(dev, state, "type ");
if (pdev->name)
printk("[%d][0x%x] type
device_suspend/n/r",device_suspend_index, pdev->name);
else
printk("[%d] type device_suspend/n/r",device_suspend_index);
error = pm_op(dev, dev->type->pm, state);
if (pdev->name)
printk("[%d][0x%x] type device_suspend
pass/n/r",device_suspend_index++, pdev->name);
else
printk("[%d] type device_suspend
pass/n/r",device_suspend_index++);
}
if (error)
goto End;
}
if (dev->bus) {
if (dev->bus->pm) {// platform bus 使用的是这种新式的电源管理方式
pm_dev_dbg(dev, state, "");
if ((u32)pdev->name & 0xC0000000)
printk("[%d][%s] device_suspend/n/r",device_suspend_index,
pdev->name);
else
printk("[%d] device_suspend/n/r",device_suspend_index);
error = pm_op(dev, dev->bus->pm, state);
// 调用回调函数dev->bus->pm->suspend(),实际上会根据device_driver中pm项是否存在来选择使用driver的老式suspend还是新式的suspend,这要看对应的driver中怎么实现的了。
if ((u32)pdev->name & 0xC0000000)
printk("[%d][%s] device_suspend
pass/n/r",device_suspend_index++, pdev->name);
else
printk("[%d] device_suspend pass/n/r",device_suspend_index++);
} else if (dev->bus->suspend) { // i2c bus就是用的这种老式的方式
pm_dev_dbg(dev, state, "legacy ");
if (pdev->name)
printk("[%d][0x%x] legacy
device_suspend/n/r",device_suspend_index, pdev->name);
else
printk("[%d] legacy device_suspend/n/r",device_suspend_index);
error = dev->bus->suspend(dev, state);
// 该函数总也是调用的i2c_driver的suspend函数
if (pdev->name)
printk("[%d][0x%x] legacy device_suspend
pass/n/r",device_suspend_index++, pdev->name);
else
printk("[%d] legacy device_suspend
pass/n/r",device_suspend_index++);
suspend_report_result(dev->bus->suspend, error);
}
}
End:
up(&dev->sem);
return error;
}
linux的电源管理是通过设备树架构来操作的,也就是说,suspend必须是先操作叶子节点设备,一级一级往上一次操作。标志linux中设备的分类是很细的,只不过实际使用中并没有区分开来,上面这个函数就可以看得到设备树的影子,
bus > device_type > class > device。
第二种pm test模式devices将会在函数suspend_test(TEST_DEVICES)中来确认,如果上层有下达echo devices > /sys/power/pm_test;echo mem > /sys/power/state
那么这里将会延时5s,然后返回1给函数suspend_devices_and_enter(),该函数将不 会继续往下走suspend的流程,而是直接调用函数 dpm_resume_end(PMSG_RESUME)来resume所有外设。正常的suspend模式下是 没有任何pm test模式出现的,所有这里考虑正常的suspend流程,继续往下走。