标准linu休眠和唤醒机制分析(一-四)

转自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模式的选项。

  标准linu休眠和唤醒机制分析(一-四)_第1张图片

使用上面的配置编译出来的系统,跑起来之后,进入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对应的kobjectsysfs_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,        /

}

// 而这些被封装过的属性结构体,将来会使用kobjectktype.sysfs_ops->show(store)这两个通用函数通过container_of()宏找到实际的属性结构体中的showstore函数来调用。

关于更多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 suspendlate 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);

};

经过后面的代码分析,得出了如下结论:

休眠唤醒过程依次会执行的函数是:beginprepareprepare_late,enter,wakefinishend同颜色的函数执行了恰好相反的工作。休眠的时候代码执行是停留在函数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

// androidlinux内核会定义该宏,首先进入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的后三种模式都在该函数中进行测试:platformcpuscore

 

 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;

// 如果某个devsuspend出错了,那么将不会执行后续devsuspend函数,将会直接返回错误码给上级函数。

              }

              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_driverpm项是否存在来选择使用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_driversuspend函数

                     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第三、四、五阶段:platformprocessorcore

static int suspend_enter(suspend_state_t state)

{

       int error;

 

       if (suspend_ops->prepare) {

 // 平台特定的函数,mtkpm.c, 有定义,对pmiccpu 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))      // suspend3阶段到此为止

              goto Platform_wake;

 

       error = disable_nonboot_cpus(); // disable nonboot cpus

       if (error || suspend_test(TEST_CPUS))  // suspend4阶段到此为止

              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))               //suspend5阶段到此为止

                     error = suspend_ops->enter(state);           

// 真正才进入suspend,调用的函数时平台特定的suspend enter函数,

//  mtkpm.c, 在下面列出mtk平台的该函数实现,供分析:

                     // 如果有唤醒源被操作,那么处理将会被wakeup,先做一些平台相                       

//  关的动作,最后从函数suspend_ops->enter()中返回,这之后的唤                        

// 醒操作实际上是按照suspend流程的相反顺序的来走的。

sysdev_resume();         // resuem system devices

// 跳到本文档最后面,将会有一个总结,这里会展示出正常的suspendresume的时候函数调用

       }

 

       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

 

你可能感兴趣的:(linux,PM)