标准linu休眠和唤醒机制分析(三) MTKLinuxCC++C#
五、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