这一节回顾一下以前学习的Linux的电源管理模块,整个流程如下:
1:内核中的相关配置
在3.4.43内核配置中,Linux/arm 3.4.43 Kernel Configuration:
Power management options --->
[*] Suspend to RAM and standby //STR的支持
[*] Run-time PM core functionality
2:用户的接口:sysfs接口,各个kobj_attribute
相关源码位于:kernel/power/main.c
static int __init pm_init(void)
{
int error = pm_start_workqueue(); //如果使能CONFIG_PM_RUNTIME,这函数将分配一个workqueue,否则函数为NULL.
if (error)
return error;
hibernate_image_size_init(); //以下两个函数与hibernate有关,也就是suspend to disk.
hibernate_reserved_size_init();
power_kobj = kobject_create_and_add("power", NULL); //建立一个名为"power"的kobject,并注册到sysfs
if (!power_kobj)
return -ENOMEM;
return sysfs_create_group(power_kobj, &attr_group); //在"power"的目录kobject下建立属性组。
}
重点在于attr_group, 看下面代码:
static struct attribute * g[] = {
&state_attr.attr,
#ifdef CONFIG_PM_TRACE //没有打开
&pm_trace_attr.attr,
&pm_trace_dev_match_attr.attr,
#endif
#ifdef CONFIG_PM_SLEEP //打开
&pm_async_attr.attr,
&wakeup_count_attr.attr,
#ifdef CONFIG_PM_DEBUG //可以打开
&pm_test_attr.attr,
#endif
#endif
NULL,
};
static struct attribute_group attr_group = {
.attrs = g,
};
power_attr(pm_async);
power_attr(pm_test);
power_attr(state);
power_attr(wakeup_count);
在kernel/power/power.h
#define power_attr(_name) \
static struct kobj_attribute _name##_attr = { \
.attr = { \
.name = __stringify(_name), \
.mode = 0644, \
}, \
.show = _name##_show, \
.store = _name##_store, \
}
展开这些宏,发现声明很多kobj_attribute, kobj_attribute在sysfs中呈现为一个文件,而kobject呈现为sysfs中的目录。读kobj_attribute的文件, kobj_attribute.show()将被调用,而写kobj_attribute的文件, kobj_attribute.store()将被调用,也就是说读写/sys/power下的attribute文件,相关的sho(),store()函数将会被调用。
3:各个kobj属性文件的分析
1):如果使能了CONFIG_PM_DEBUG,哪么将定义power_attr(pm_test);,创建sys/power/pm_test属性文件。
终端下执行命令:cat /sys/power/pm_test,最终会调用函数pm_test_show(),终端上将打印出所有PM的阶段模式,并用[]表示出当前设置了的测试模式。Linux下定义了PM以下6种level:[none] core processors platform devices freezer。
执行命令:echo platform > /sys/power/pm_test最终会调用函数pm_test_store(),设置要测试的PM level为platform。pm_test_store()函数内部将会使用pm_test_level记录要测试的level。在suspend过程中,如果所处level与当前测试的相等哪么就会在终端打印出suspend debug: Waiting for 5 seconds。
2);如果使能了CONFIG_PM_SLEEP,定义了power_attr(wakeup_count);及power_attr(pm_async)。创建sys/power/wakeup_count,及sys/power/pm_async属性文件。
其中wakeup_count是用于检测当前是否有唤醒的event,如果当前有唤醒event,正在进行的suspend将会中止,使用一般是先读出当前wakeup event个数,然后重新写入。pm_async属性文件是用于device的suspend/resume是否同步,默认值为1,设置0为不同步,其他值为同步。
3):最关键的一个属性文件:power_attr(state)。
对sys/power/state读,state_show()函数将被调用,显示当前系统支持电源模式,现在主要包括以下模式:standby' (Power-On Suspend), 'mem' (Suspend-to-RAM), and 'disk' (Suspend-to-Disk),其中嵌入式系统中一般都没有'disk'模式。
对sys/power/state写操作,例如echo mem > /sys/power/state。如果所写的字符串是上面函数打出的所支持的模式,哪么将调用pm_suspend(),进入相应的模式。
state_show()函数内调用了一个函数valid_state();这个函数是平台配置的支持省电模式的验证函数,该函数定义于文件kernel/power/suspend.c
void suspend_set_ops(const struct platform_suspend_ops *ops)
{
mutex_lock(&pm_mutex);
suspend_ops = ops;
mutex_unlock(&pm_mutex);
}
bool valid_state(suspend_state_t state)
{
return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
}
实际的平台级代码将会调用suspend_set_ops()函数设置板级的struct platform_suspend_ops *ops,该结构体一般在文件arch/arm/mach-XXX/pm.c
struct platform_suspend_ops {
int (*valid)(suspend_state_t state);
int (*begin)(suspend_state_t state);
int (*prepare)(void);
int (*prepare_late)(void);
int (*enter)(suspend_state_t state);
void (*wake)(void);
void (*finish)(void);
void (*end)(void);
void (*recover)(void);
};
从函数分析得出休眠唤醒过程将会依执行平台级所注册的struct platform_suspend_ops结构体中的函数:begin,prepare,prepare_late,enter,wake,finish,end,recover。休眠的时候代码执行停留在函数enter,唤醒就是从停留的地方继续执行end。recover只有在pm_test设置为"devices"才执行。
4:pm_suspend()函数分析
suspend按pm_test可分为5个阶段:freezer,devices,platform,cups,core。
suspend代码位于:kernel/power/suspend.c,以下是解读代码:
freezer阶段:
static int enter_state(suspend_state_t state)
{
int error;
if (!valid_state(state))
return -ENODEV;
if (!mutex_trylock(&pm_mutex))
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(); //进备进入系统休眠状态,属于第一阶段freezer,主要完成工作:准备consol,调用PM通知链状态为PM_SUSPEND_PREPARE,freeze所有进程。
if (error) //准备失败,通常为freeze进程的时候某些进程拒绝进入冻结模式。
goto Unlock;
if (suspend_test(TEST_FREEZER)) //测试pm_test,如果为TEST_FREEZER,就延时5S.
goto Finish;
pr_debug("PM: Entering %s sleep\n", pm_states[state]);
pm_restrict_gfp_mask();
error = suspend_devices_and_enter(state); //进入其他阶段。
pm_restore_gfp_mask();
Finish:
pr_debug("PM: Finishing wakeup.\n");
suspend_finish(); //解决进程,发通知链。
Unlock:
mutex_unlock(&pm_mutex);
return error;
}
device阶段:
int suspend_devices_and_enter(suspend_state_t state)
{
int error;
bool wakeup = false;
if (!suspend_ops)
return -ENOSYS;
trace_machine_suspend(state);
if (suspend_ops->begin) {
error = suspend_ops->begin(state);
if (error)
goto Close;
}
suspend_console(); //suspend_console - suspend the console subsystem
ftrace_stop();
suspend_test_start(); //记录suspend开始时间
error = dpm_suspend_start(PMSG_SUSPEND); //Prepare devices for PM transition and suspend them.休眠所有外设,device阶段关键函数。下一次分析相关的数据类型。
if (error) {
printk(KERN_ERR "PM: Some devices failed to suspend\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
&& suspend_ops->suspend_again && suspend_ops->suspend_again());
Resume_devices:
suspend_test_start();
dpm_resume_end(PMSG_RESUME);
suspend_test_finish("resume devices");
ftrace_start();
resume_console();
Close:
if (suspend_ops->end)
suspend_ops->end();
trace_machine_suspend(PWR_EVENT_EXIT);
return error;
Recover_platform:
if (suspend_ops->recover)
suspend_ops->recover();
goto Resume_devices;
}
platform,cups,core阶段:
static int suspend_enter(suspend_state_t state, bool *wakeup)
{
int error;
if (suspend_ops->prepare) {
error = suspend_ops->prepare();
if (error)
goto Platform_finish;
}
error = dpm_suspend_end(PMSG_SUSPEND);
if (error) {
printk(KERN_ERR "PM: Some devices failed to power down\n");
goto Platform_finish;
}
if (suspend_ops->prepare_late) {
error = suspend_ops->prepare_late(); //如有,调用板级代码 platform
if (error)
goto Platform_wake;
}
if (suspend_test(TEST_PLATFORM))
goto Platform_wake;
error = disable_nonboot_cpus(); //cpu
if (error || suspend_test(TEST_CPUS))
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)) {
error = suspend_ops->enter(state); //板级代码,进入真正的休眠,代码的运行将停在这里,直到唤醒。
events_check_enabled = false;
}
syscore_resume(); //resume
}
arch_suspend_enable_irqs();
BUG_ON(irqs_disabled());
Enable_cpus:
enable_nonboot_cpus();
Platform_wake:
if (suspend_ops->wake)
suspend_ops->wake();
dpm_resume_start(PMSG_RESUME);
Platform_finish:
if (suspend_ops->finish)
suspend_ops->finish();
return error;
}
5:device设备模型中电源管理部分
有两个电源管理相关的结构体dev_pm_info,dev_pm_domain,定义于kernel/include/linux/pm.h。这两个结构广泛嵌入于linux设备模型的结构体:struct device, struct device_type, struct class, struct class, struct device_driver, struct bus_type.
其中device中的struct dev_pm_info power, 将设备纳入电源管理范围,记录电源管理的相关信息,在注册设备的时候调用device_add()向sysfs添加power接口和注册电源管理系统。关键函数有dpm_sysfs_add(),device_pm_add();