转自http://blog.csdn.net/lizhiguo0532/article/details/6453529
说明:
1. Based on linux2.6.32, only for mem(SDR)
2. 有兴趣请先参考阅读: 电源管理方案APM和ACPI比较.doc
Linux系统的休眠与唤醒简介.doc
3. 本文先研究标准linux的休眠与唤醒,android对这部分的增改在另一篇文章中讨论
4. 基于手上的一个项目来讨论,这里只讨论共性的地方
虽然linux支持三种省电模式:standby、suspend to ram、suspend to disk,但是在使用电池供电的手持设备上,几乎所有的方案都只支持STR模式(也有同时支持standby模式的),因为STD模式需要有交换分区的支持,但是像手机类的嵌入式设备,他们普遍使用nand来存储数据和代码,而且其上使用的文件系统yaffs一般都没有划分交换分区,所以手机类设备上的linux都没有支持STD省电模式。
一、项目power相关的配置
目前我手上的项目的linux电源管理方案配置如下,.config文件的截图,当然也可以通过make menuconfig使用图形化来配置:
#
# CPU Power Management
#
# CONFIG_CPU_IDLE is not set
#
# Power management options
#
CONFIG_PM=y
# CONFIG_PM_DEBUG is not set
CONFIG_PM_SLEEP=y
CONFIG_SUSPEND=y
CONFIG_SUSPEND_FREEZER=y
CONFIG_HAS_WAKELOCK=y
CONFIG_HAS_EARLYSUSPEND=y
CONFIG_WAKELOCK=y
CONFIG_WAKELOCK_STAT=y
CONFIG_USER_WAKELOCK=y
CONFIG_EARLYSUSPEND=y
# CONFIG_NO_USER_SPACE_SCREEN_ACCESS_CONTROL is not set
# CONFIG_CONSOLE_EARLYSUSPEND is not set
CONFIG_FB_EARLYSUSPEND=y
# CONFIG_APM_EMULATION is not set
# CONFIG_PM_RUNTIME is not set
CONFIG_ARCH_SUSPEND_POSSIBLE=y
CONFIG_NET=y
上面的配置对应下图中的下半部分图形化配置。。。,看来是直接在Kconfig文件中删除了配置STD模式的选项。
使用上面的配置编译出来的系统,跑起来之后,进入sys目录可以看到相关的接口:
# pwd
/sys/power
# ls
state wake_lock wake_unlock wait_for_fb_sleep wait_for_fb_wake
# cat state
mem
如果配置了宏CONFIG_PM_DEBUG,那么在power目录下会多出一个pm_test文件,cat pm_test后,列出的测试选项有:[none] core processors platform devices freezer。关于这个test模式的使用,可以参考kernel文档:/kernel/documentation/power/Basic-pm-debugging.txt
这个文档我也有详细的阅读和分析。
二、sys/power和相关属性文件创建
系统bootup时候在sys下新建power和相关属性文件,相关源码位置:
kernel/kernel/power/main.c
static int __init pm_init(void)
{
int error = pm_start_workqueue();// CONFIG_PM_RUNTIME not set, so this fun is null
if (error)
return error;
power_kobj = kobject_create_and_add("power", NULL);
// 建立power对应的kobject和sysfs_dirent对象,同时建立联系:kobject.sd =
// &sysfs_dirent, sysfs_dirent.s_dir->kobj = &kobject。
if (!power_kobj)
return -ENOMEM;
return sysfs_create_group(power_kobj, &attr_group);
// 建立一组属性文件,可以在power下建立一个子目录来存放这些属性文件, //不过需要在结构体attr_group中指定name,否则直接将这些属性文件放在 // power_kobj对应的目录下。
}
该函数是core_init级别的。
core_initcall(pm_init); // 看的出来,该函数是很早就被调用,initcall等级为1
static struct attribute_group attr_group = {
.attrs = g,
};
struct attribute_group {
const char *name;
mode_t (*is_visible)(struct kobject *,
struct attribute *, int);
struct attribute **attrs;
};
// 属性文件都是以最基本得属性结构struct attribute来建立的
static struct attribute * g[] = {
&state_attr.attr,
#ifdef CONFIG_PM_TRACE // not set
&pm_trace_attr.attr,
#endif
#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_PM_DEBUG) // not set
&pm_test_attr.attr,
#endif
#ifdef CONFIG_USER_WAKELOCK // set
&wake_lock_attr.attr,
&wake_unlock_attr.attr,
#endif
NULL,
};
#ifdef CONFIG_PM_SLEEP
#ifdef CONFIG_PM_DEBUG
power_attr(pm_test);
#endif
#endif
power_attr(state);
#ifdef CONFIG_PM_TRACE
power_attr(pm_trace);
#endif
#ifdef CONFIG_USER_WAKELOCK
power_attr(wake_lock);
power_attr(wake_unlock);
#endif
#define power_attr(_name) /
static struct kobj_attribute _name##_attr = { /
.attr = { /
.name = __stringify(_name), /
.mode = 0644, /
}, /
.show = _name##_show, /
.store = _name##_store, /
}
// 而这些被封装过的属性结构体,将来会使用kobject的ktype.sysfs_ops->show(store)这两个通用函数通过container_of()宏找到实际的属性结构体中的show和store函数来调用。
关于更多sysfs的内容,请查看其他关于这部分内容的详细解析文档。
一个/sys/power/的sysfs示例如下:
/sys/power$ ls
disk image_size pm_async pm_test pm_trace pm_trace_dev_match resume state wakeup_count
三、pm_test属性文件读写
int pm_test_level = TEST_NONE;
static const char * const pm_tests[__TEST_AFTER_LAST] = {
[TEST_NONE] = "none",
[TEST_CORE] = "core",
[TEST_CPUS] = "processors",
[TEST_PLATFORM] = "platform",
[TEST_DEVICES] = "devices",
[TEST_FREEZER] = "freezer",
};
// core >> processors >> platform >> devices >> freezer, 控制范围示意。
cat pm_test的时候最终会调用函数pm_test_show(),在终端上打印出上面数组中的字符串,当前的模式用[]表示出来。
echo devices > pm_test的时候会最终调用到函数pm_test_store()中去,该函数中设置全局变量pm_test_level的值,可以是0-5,分别代表上none ~ freezer。该全局变量会在后面的suspend和resume中被引用到。
memchr函数说明:
原型:extern void *memchr(void *buf, char ch, unsigned int count);
用法:#include <string.h>
功能:从buf所指内存区域的前count个字节查找字符ch。
说明:当第一次遇到字符ch时停止查找。如果成功,返回指向字符ch的指针;否则返回NULL。
四、state属性文件
power_attr(state)宏定义了一个struct kobj_attribute结构体state_attr:
static struct kobj_attribute state_attr = {
.attr = {
.name = __stringify(state),
.mode = 0644,
},
.show = state_show,
.store = state_store,
}
kobj_attribute结构体封装了struct attribute结构体,新建属性文件是依据struct attribute结构体。最终通过函数kobj_attr_show和kobj_attr_store回调到实际的show和store函数(kobject.c)。
state_show()函数主要是显示当前系统支持哪几种省电模式:on,standby,mem,disk。
static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
char *s = buf;
#ifdef CONFIG_SUSPEND //def
int i;
for (i = 0; i < PM_SUSPEND_MAX; i++) {
if (pm_states[i] &&valid_state(i))
s += sprintf(s,"%s ", pm_states[i]);
}
#endif
#ifdef CONFIG_HIBERNATION // undef, don't support STD mode
s += sprintf(s, "%s/n", "disk");
#else
if (s != buf)
/* convert the last space to a newline */
*(s-1) = '/n';
#endif
return (s - buf);
}
@ kernel/include/linux/suspend.h
#define PM_SUSPEND_ON ((__force suspend_state_t) 0)
#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 1)
#define PM_SUSPEND_MEM ((__force suspend_state_t) 3)
#define PM_SUSPEND_DISK ((__force suspend_state_t) 4)
#define PM_SUSPEND_MAX ((__force suspend_state_t) 5)
@ kernel/kernel/power/suspend.c
const char *const pm_states[PM_SUSPEND_MAX] = {
#ifdef CONFIG_EARLYSUSPEND // android修改了标准linux的休眠唤醒机制,增加了eraly suspend和late resume机制,如果是android内核,则这个宏是需要定义的。
[PM_SUSPEND_ON] = "on",
#endif
[PM_SUSPEND_STANDBY] = "standby",
[PM_SUSPEND_MEM] = "mem",
};
该函数中值得注意的地方应该是valid_state(i),这个函数是用户配置的支持省电模式的验证函数,如果没有这个验证过程,cat时候打印出来的模式则是on standby mem,给上层用户的使用造成困扰。
那这个valid_state()函数在哪里定义的呢?一般定义于文件kernel/kernel/power/suspend.c
static struct platform_suspend_ops *suspend_ops;
void suspend_set_ops(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);
}
而实际平台的platform_suspend_ops结构体一般都是在文件arch/arm/mach-xxxx/pm.c中进行定义,对于mtk的平台是文件mtkpm.c,如下:
@ kernel/include/linux/suspend.h
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);
};
经过后面的代码分析,得出了如下结论:
休眠唤醒过程依次会执行的函数是:begin,prepare,prepare_late,enter,wake,finish,end。同颜色的函数执行了恰好相反的工作。休眠的时候代码执行是停留在函数enter中,wake之后也是从suspend的时候停留的地方继续运行。
至于recover函数貌似只有在pm_test处于devices的模式下,才会被调用到。
@ kernel/arch/arm/mach-mt6516/mtkpm.c
static struct platform_suspend_ops mtk_pm_ops = {
.valid = mtk_pm_state_valid,
.begin = mtk_pm_begin,
.prepare = mtk_pm_prepare,
.enter = mtk_pm_enter,
.finish = mtk_pm_finish,
.end = mtk_pm_end,
};
static int mtk_pm_state_valid(suspend_state_t pm_state)
{
return pm_state == PM_SUSPEND_MEM ;
}
void mtk_pm_init(void)
{
_Chip_PM_init();
/* Register and set suspend operation */
suspend_set_ops(&mtk_pm_ops);
}
而函数mtk_pm_init()是在函数mt6516_init_irq()中调用。可以看出该平台只支持mem的省电模式。
state_store()函数:
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t n)
{
#ifdef CONFIG_SUSPEND // set
#ifdef CONFIG_EARLYSUSPEND //对标准linux而言,这个宏不存在
suspend_state_t state = PM_SUSPEND_ON;
#else
suspend_state_t state = PM_SUSPEND_STANDBY;
#endif
const char * const *s;
#endif
char *p;
int len;
int error = -EINVAL;
p = memchr(buf, '/n', n);
len = p ? p - buf : n;
/* First, check if we are requested to hibernate */
if (len == 4 && !strncmp(buf, "disk", len)) {
error = hibernate(); //如果值是disk,那么进入STD模式,该模式暂不讨论
goto Exit;
}
#ifdef CONFIG_SUSPEND // def
for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) {
if (*s && len == strlen(*s) && !strncmp(buf, *s, len))
break;
}
if (state < PM_SUSPEND_MAX && *s)
#ifdef CONFIG_EARLYSUSPEND
// android的linux内核会定义该宏,首先进入eraly suspend模式
if (state == PM_SUSPEND_ON || valid_state(state)) {
error = 0;
request_suspend_state(state);
}
#else // 标准linux内核直接enter_state()函数
error = enter_state(state); // kernel/kernel/power/suspend.c
#endif
#endif
Exit:
return error ? error : n;
}
cat mem > /sys/power/state
cat disk > /sys/power/state
使系统进入休眠。
五、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流程,继续往下走。
suspend第三、四、五阶段:platform、processor、core
static int suspend_enter(suspend_state_t state)
{
int error;
if (suspend_ops->prepare) {
// 平台特定的函数,mtkpm.c, 有定义,对pmic和cpu dll的一些设置
error = suspend_ops->prepare();
if (error)
return error;
}
error = dpm_suspend_noirq(PMSG_SUSPEND);
// 对于一些non-sysdev devices,需要调用禁止中断的dpm_suspend函数来suspend那些设备
if (error) {
printk(KERN_ERR "PM: Some devices failed to power down/n");
goto Platfrom_finish;
}
if (suspend_ops->prepare_late) {//这里没定义
error = suspend_ops->prepare_late();
if (error)
goto Power_up_devices;
}
if (suspend_test(TEST_PLATFORM)) // suspend第3阶段到此为止
goto Platform_wake;
error = disable_nonboot_cpus(); // disable nonboot cpus
if (error || suspend_test(TEST_CPUS)) // suspend第4阶段到此为止
goto Enable_cpus;
arch_suspend_disable_irqs(); //中断禁止
BUG_ON(!irqs_disabled());
error = sysdev_suspend(PMSG_SUSPEND); // kernel/driver/base/sys.c
// suspend system devices
if (!error) {
if (!suspend_test(TEST_CORE)) //suspend第5阶段到此为止
error = suspend_ops->enter(state);
// 真正才进入suspend,调用的函数时平台特定的suspend enter函数,
// mtkpm.c, 在下面列出mtk平台的该函数实现,供分析:
// 如果有唤醒源被操作,那么处理将会被wakeup,先做一些平台相
// 关的动作,最后从函数suspend_ops->enter()中返回,这之后的唤
// 醒操作实际上是按照suspend流程的相反顺序的来走的。
sysdev_resume(); // resuem system devices
// 跳到本文档最后面,将会有一个总结,这里会展示出正常的suspend和resume的时候函数调用
}
arch_suspend_enable_irqs();
BUG_ON(irqs_disabled());
Enable_cpus:
enable_nonboot_cpus();
Platform_wake:
if (suspend_ops->wake) //平台无定义
suspend_ops->wake();
Power_up_devices:
dpm_resume_noirq(PMSG_RESUME);
Platfrom_finish:
if (suspend_ops->finish)//做和函数suspend_ops->prepare()相反的工作
suspend_ops->finish();
return error;
}
static int mtk_pm_enter(suspend_state_t state)
{
_Chip_pm_enter(state);
return 0;
}
int _Chip_pm_enter(suspend_state_t state)
{
MSG_FUNC_ENTRY();
printk("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@/n");
printk("_Chip_pm_enter @@@@@@@@@@@@@@@@@@@@@@/n");
printk(" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@/n");
/* ensure the debug is initialised (if enabled) */
switch (state)
{
case PM_SUSPEND_ON:
MSG(SUSP,"mt6516_pm_enter PM_SUSPEND_ON/n/r");
break;
case PM_SUSPEND_STANDBY:
MSG(SUSP,"mt6516_pm_enter PM_SUSPEND_STANDBY/n/r");
break;
case PM_SUSPEND_MEM: //只支持mem的系统省电模式
MSG(SUSP,"mt6516_pm_enter PM_SUSPEND_MEM/n/r");
if (g_ChipVer == CHIP_VER_ECO_2)
mt6516_pm_SuspendEnter();
// 让cpu进入省电模式的函数,真正休眠之后,执行的代码会停在这个函数中,直到外部有EINT将其cpu唤醒,停下来的代码才继续执行,也就是正常按下了唤醒键的时候。
break;
case PM_SUSPEND_MAX:
MSG(SUSP,"mt6516_pm_enter PM_SUSPEND_MAX/n/r");
MSG(SUSP,"Not support for MT6516/n/r");
break;
default:
MSG(SUSP,"mt6516_pm_enter Error state/n/r");
break;
}
return 0;
}
void mt6516_pm_SuspendEnter(void)
{
UINT32 u32TCM = 0xF0400000;
UINT32 u4SuspendAddr = 0;
UINT32 u4Status, u4BATVol;
UINT32 counter = 0;
/* Check Chip Version*/
if (g_ChipVer == CHIP_VER_ECO_1)
u4SuspendAddr = u32TCM;
else if(g_ChipVer == CHIP_VER_ECO_2)
u4SuspendAddr = __virt_to_phys((unsigned long)MT6516_CPUSuspend);
/*wifi low power optimization : shutdown MCPLL & VSDIO */
wifi_lowpower_opt(TRUE);
/* Check PM related register*/
mt6516_pm_RegDump();
//mt6326_check_power();
DRV_WriteReg32(APMCUSYS_PDN_SET0,0x04200000);
/* STEP7: Set AP_SM_CNF(DxF003C22C) to wanted wake-up source.设置唤醒源*/
#if defined(PLATFORM_EVB)
mt6516_pm_SetWakeSrc((1<< WS_KP)|(1<<WS_EINT)|(1<<WS_RTC));
#elif defined(PMIC_BL_SETTING)
mt6516_pm_SetWakeSrc((1<<
WS_KP)|(1<<WS_EINT)|(1<<WS_CCIF)|(1<<WS_SM)|(1<<WS_RTC));
#else
mt6516_pm_SetWakeSrc((1<<WS_EINT)|(1<<WS_CCIF)|(1<<WS_SM)|(1<<WS_RTC));
//mt6516_pm_SetWakeSrc((1<<WS_SM));
#endif
/* Save interrupt masks*/
irqMask_L = *MT6516_IRQ_MASKL;
irqMask_H = *MT6516_IRQ_MASKH;
mt6516_pm_Maskinterrupt(); // 20100316 James
while(1)
{
#ifdef AP_MD_EINT_SHARE_DATA
/* Update Sleep flag*/
mt6516_EINT_SetMDShareInfo();
mt6516_pm_SleepWorkAround();
#endif
/* Enter suspend mode, mt6516_slpctrl.s */
if ( g_Sleep_lock <= 0 )
u4Status = MT6516_CPUSuspend (u4SuspendAddr, u32TCM);
else
MSG(SUSP,"Someone lock sleep/n/r");
#ifdef AP_MD_EINT_SHARE_DATA
mt6516_pm_SleepWorkAroundUp();
#endif
/* Check Sleep status*/
u4Status = mt6516_pm_CheckStatus();
if (u4Status == RET_WAKE_TIMEOUT)
{
#ifndef PLATFORM_EVB
DRV_WriteReg32(APMCUSYS_PDN_CLR0,0x04200000);
u4BATVol = (mt6516_pm_GetOneChannelValue(VBAT_CHANNEL,VBAT_COUNT)/VBAT_COUNT);
DRV_WriteReg32(APMCUSYS_PDN_SET0,0x04200000);
MSG(SUSP,"counter = %d, vbat = %d/n/r",counter++, u4BATVol);
if(u4BATVol <= LOW_BAT_ALARM)
{
MSG(SUSP,"Battery Low!!Power off/n/r");
bBAT_UVLO = TRUE;
goto SLP_EXIT;
}
#endif
}
else
{
MSG(SUSP,"leave sleep, wakeup!!/n/r");
goto SLP_EXIT;
//break;
}
}
SLP_EXIT:
wifi_lowpower_opt(FALSE);
/* Restore interrupt mask ; */
*MT6516_IRQ_MASKL = irqMask_L;
*MT6516_IRQ_MASKH = irqMask_H;
}
函数MT6516_CPUSuspend (u4SuspendAddr, u32TCM)是一段汇编代码,在文件:
Kernel/arch/arm/amch-mt6516/mt6516_slpctrl.S中。下面是这段汇编代码片段,看一看也蛮有意思,因为处理进入low power模式之后,是停留在该函数之中的。
ENTRY(MT6516_CPUSuspend)
stmfd sp!, {r4-r12, lr}
// r0 = MT6516_CPUSuspend physical address,
// r1 = TCM address
mov r4, r0
mov r9, r1
// Set SVC mode
mrs r0, cpsr
bic r0, r0, #MODE_MASK1
orr r1, r0, #Mode_SVC
// Set I/F bit, disable IRQ and FIQ
orr r1, r1, #I_Bit|F_Bit
// Update CPSR
msr cpsr_cxsf, r1
// calculate the physical address of instruction after disable MMU
ldr r0, =PhysicalPart
ldr r1, =MT6516_CPUSuspend
sub r0, r0, r1
mov r1, r4
// Now r0 is the physical address of PhysicalPart
add r0, r0, r1
...
...
// Power down Cache and MMU, MCU_MEM_PDN
ldr r0, =0xF0001308
ldr r1, [r0]
// ldr r1, =0xFFFFFFFF
orr r1, r1, #0x0F
str r1, [r0]
// STEP1: Set AP SLEEP (IRQ CODE: 0x36) to level sensitive on CIRQ.
// already done when system start.
// STEP2: Unmask AP SLEEP CTRL interrupt.
// already done at mt6516_pm_Maskinterrupt.
// STEP3: EOI AP SLEEP interrupt.
// already done at mt6516_pm_Maskinterrupt.
// STEP4: Read clear AP_SM_STA (OxF003C21C).
// already done at mt6516_pm_Maskinterrupt.
// STEP5: Set AP_SM_PAUSE_M(0x8003C200) and AP_SM_PAUSE_L(0x8003C204) for sleep duration. 16 seconds as default
...
// STEP6: Set AP_SM_CLK_SETTLE(0xF003C208) to 0x64. Must over 5ms
...
// STEP7: Set AP_SM_CNF(DxF003C22C) to wanted wake-up source. (TP, GPT, MSDC, RTC, EINT, KP or SM)
// already done at mt6516_pm_SuspendEnter
// STEP8: Set AP_SM_CON[1]:PAUSE_START to 1 to enable AP sleep controller.
...
// STEP9: Execute the CP15 command(MCR p15, 0, r0, c7, c0, 4),
// then ARM9 MCU enters low power state
// and STANDBYWFI signal becomes HIGH. CLOCK_OFF signal is issued to Clock Management Unit,
// and then AP MCU Sub-system clock is gated and VCXO OFF signal is issued to AP Sleep Controller.
mov r0, #0
mcr p15, 0, r0,c7,c0,4
// wait till interrupt occurs
// polling AP_SM_STA
mov r2, #0
mov r3, #0x10
15:
//mov r10, r1
// Power up I-Cache
...
//delay
...
// Power up I-Cache upper 16KB
...
//delay
...
// Power up D-Cache
...
//delay
...
// Power up D-Cache upper 16KB
...
//delay
...
// Clean and invalid DCache
// Invalidate instruction cache
// TCM_START_UA saved in r9
mov r2, r9
add r1, r1, r2
// make sure no stall on¨mov pc,r0〃 below
cmp r1, #0
// restore MMU
mov r4, #0
// access domain 0
// TTB
// flush TLBs
// Turn on MMU
//test
mcr p15, 0, r6, c1, c0, 0
//mov pc, r1
nop
nop
VirtualPart:
nop
nop
mov r0, r10
ldmia sp!, {r4-r12, lr}
mov pc, lr
Nop
六、系统正常suspend和resume时函数调用和配对
enter_state(state)
--> sys_sync()
--> suspend_prepare()
--> pm_prepare_console()
--> pm_notifier_call_chain(PM_SUSPEND_PREPARE)
--> usermodehelper_disable()
--> suspend_freeze_processes()
--> suspend_devices_and_enter(state)
-->suspend_ops->begin(state)
--> _Chip_pm_begin()
--> suspend_console() // 此后串口无信息出来,缓存起来等后面resume打出
--> dpm_suspend_start(PMSG_SUSPEND)
--> dpm_prepare(state)
--> device_prepare(dev, state)
--> dpm_suspend(state)
--> device_suspend(dev, state)
--> suspend_enter(state)
--> suspend_ops->prepare()
--> _Chip_pm_prepare()
--> SetARM9Freq(DIV_4_104)
--> dpm_suspend_noirq(PMSG_SUSPEND)
--> suspend_ops->prepare_late() // 无定义
--> disable_nonboot_cpus()
--> arch_suspend_disable_irqs()
--> sysdev_suspend(PMSG_SUSPEND)
--> suspend_ops->enter(state)
--> _Chip_pm_enter(state)
--> mt6516_pm_SuspendEnter()
--> MT6516_CPUSuspend() //汇编函数,suspend cpu
<-- MT6516_CPUSuspend() //汇编函数,resume cpu
<-- mt6516_pm_CheckStatus()
<-- return 0
<-- return 0
<-- sysdev_resume()
<-- arch_suspend_enable_irqs()
<-- enable_nonboot_cpus()
<-- suspend_ops->wake() // 无定义
<-- dpm_resume_noirq(PMSG_RESUME)
<-- suspend_ops->finish()
<-- _Chip_pm_finish()
<-- SetARM9Freq(DIV_1_416)
<-- return 0
<-- return 0
<-- dpm_resume_end(PMSG_RESUME)
<-- dpm_resume(state)
<-- device_resume(dev, state)
<-- dpm_complete(state)
<-- device_complete(dev, state)
<-- resume_console() // 打印出缓存中的信息
<-- suspend_ops->end()
<-- return 0
<-- suspend_finish()
<-- suspend_thaw_processes()
<-- usermodehelper_enable()
<-- pm_notifier_call_chain(PM_POST_SUSPEND)
<-- pm_restore_console()
<-- return 0