设备模型8之电源管理(一)(linux-3.2.36,从apm_bios分析suspend和resume调用)

来源:互联网 发布:在淘宝买药要货到付款 编辑:程序博客网 时间:2024/06/16 15:58

刚在论坛转了一下,写这个主题的也有不少且写的很好,我就直接推荐吧,连接:

http://blog.csdn.net/myxmu/article/details/7955232

http://blog.csdn.net/myxmu/article/details/7955321

http://blog.csdn.net/myxmu/article/details/7955375

http://blog.csdn.net/myxmu/article/details/7955982

http://blog.csdn.net/dwyane_zhang/article/details/7099731

高手真多啊,如果我就贴这几个连接,肯定被骂成标题党。

为了我的清白,我就写点什么吧。

首先ACPIInteli386x86_64IA64)平台的标准固件规范。看过我之前的文章知道我一直用的是arm平台。所以我就用APM来说,我会告诉你系统如何调用驱动的suspendresume

APM是基于bios的,但arm中没有这个玩意,不过linux为我们做了一个字符设备。/dev/apm_bios,源码在drivers/char/apm-emulation.c//linux-3.2.36

有人可能会说我经常看到echo “standby” >/sys/power/state这是这么回事。

源码

static struct attribute * g[] = {

        &state_attr.attr,

state_attrstore方法会判断你的输入并调用enter_state,我们的apm_bios也会调用enter_state(下面说它),我想看过我之前的文章对store的调用很熟悉吧。这不是我们要走的路,我们还是看apm_bios

首先apm_bios初始化会建立kapmd线程,它会在kapmd_queue不为空时,获取queue的事件进行处理。这个线程说的不多,不过以后我们在一些实例中看到。

如何调用suspend,我们就从apm_emulation.cioctl入手,对于字符设备ioctl的调用不用细说了吧。

static long

apm_ioctl(struct file *filp, u_int cmd, u_long arg)

{

        …

        switch (cmd) {

        case APM_IOC_SUSPEND:

                mutex_lock(&state_lock);

 

                as->suspend_result = -EINTR;

 

                switch (as->suspend_state) {

                case SUSPEND_READ:

                        …

                        //调用了apm_read,待确认

                        break;

                case SUSPEND_ACKTO:

                        ...//TO就是timeout

                        break;

                default:

                        as->suspend_state = SUSPEND_WAIT;//表示调用了suspend等待resume

                        mutex_unlock(&state_lock);

 

                        /*

                         * Otherwise it is a request to suspend the system.

                         * Just invoke pm_suspend(), we'll handle it from

                         * there via the notifier.

                         */

                        as->suspend_result = pm_suspend(PM_SUSPEND_MEM);

                }

 

                mutex_lock(&state_lock);

                err = as->suspend_result;

                as->suspend_state = SUSPEND_NONE;//清除状态

                mutex_unlock(&state_lock);

                break;

        }

 

        return err;

}

主要调用pm_suspend

pm_suspend()主要调用enter_state()

kernel/power/suspend.c

int enter_state(suspend_state_t state)

{

        …

        error = suspend_prepare();//

        if (error)

                goto Unlock;

 

        if (suspend_test(TEST_FREEZER))//如果你的内核没选suspend debug,它就是空函数,否则它会把测试等级与TEST_FREEZER做比较,相等的话延时5000ms

                goto Finish;

 

        …

        error = suspend_devices_and_enter(state);

        …

}

suspend_prepare()绝不是一个善类,干了不少坏事。

static int suspend_prepare(void)

{

        int error;

 

        if (!suspend_ops || !suspend_ops->enter)

                return -EPERM;

 

        pm_prepare_console();//分配一个虚拟终端来输出信息,这个会调用drivers/tty/vt。当然是否分配也是要配置的。

 

        error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);//调用通知连,它会通知所有注册的模块。在drivers/char/apm-emulation.c中会调用static int apm_suspend_notifier(struct notifier_block *nb,unsigned long event,void *dummy)event = PM_SUSPEND_PREPARE。它会把用户挂起事件加入kapmd_queuekapmd线程会去处理。还会判断超时。也就是通知进入suspend了。

        if (error)

                goto Finish;

       error = usermodehelper_disable();//关闭掉用户态的helper进程,helper进程我们在热插拔时有说,能让内核直接新建和运行用户空间程序。

 

        if (error)

                goto Finish;

        error = suspend_freeze_processes();//冻结所有进程,并保存所有进程当前的状态。

 

        if (error) {

                suspend_stats.failed_freeze++;

                dpm_save_failed_step(SUSPEND_FREEZE);

//也许有一些进程会拒绝进入冻结状态当有这样的进程存在的时候会导致冻结失败,此函数就会放弃冻结进程,并且解冻刚才冻结的所有进程

        } else

                return 0;

 

        suspend_thaw_processes();

        usermodehelper_enable();

 Finish:

        pm_notifier_call_chain(PM_POST_SUSPEND);//退出suspend的通知

        pm_restore_console();

        return error;

}

suspend_devices_and_enter()是我们的绝对主力。

int suspend_devices_and_enter(suspend_state_t state)

{

        int error;

        bool wakeup = false;

        //suspend_ops和平台有关,这个我们下面看

        if (!suspend_ops)

                return -ENOSYS;

 

        trace_machine_suspend(state);//我找了整个目录都没找到这个函数

        if (suspend_ops->begin) {

                error = suspend_ops->begin(state);

                if (error)

                        goto Close;

        }

        suspend_console();//kernel/printk.c如果console_suspend_enabled==0,则不会执行

/*

看看console_suspend_enabled如何设为0

static int __init console_suspend_disable(char *str)

{

       console_suspend_enabled = 0;

       return 1;

}

__setup("no_console_suspend", console_suspend_disable);

可以看出启动参数要有no_console_suspend,不然consolesuspend就不能输出信息了

*/

//test不管了

        suspend_test_start();

        error = dpm_suspend_start(PMSG_SUSPEND);// suspend all devices,外设休眠,这是设备中suspend调用的重点,我们下面看。

        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());

 

        …

}

 

int dpm_suspend_start(pm_message_t state)

{

        int error;

 

        error = dpm_prepare(state);

        if (error) {

                suspend_stats.failed_prepare++;

                dpm_save_failed_step(SUSPEND_PREPARE);

        } else

                error = dpm_suspend(state);

        return error;

}

 

suspend要经过,prepare->suspend->suspend_noirq;我们看这个过程

prepare阶段主要是通过阻止新设备注册来防止竟态的发生;如果此时要注册子设备,PM的核心将会不知道一个设备的所有子设备已经被suspend。(相反,设备可以在任何时刻被注销。)不像suspend其他的阶段,prepare阶段设备树会自顶向下进行扫描。

int dpm_prepare(pm_message_t state)

{

        int error = 0;

 

        might_sleep();

 

        mutex_lock(&dpm_list_mtx);

        while (!list_empty(&dpm_list)) {

                struct device *dev = to_device(dpm_list.next);

                //上面是编历链表并或取dev

                get_device(dev);//引用记数加一

                mutex_unlock(&dpm_list_mtx);

 

                error = device_prepare(dev, state);//下面看

 

                mutex_lock(&dpm_list_mtx);

                if (error) {

                        if (error == -EAGAIN) {

                                put_device(dev);//引用记数减一

                                error = 0;

                                continue;

                        }

                        printk(KERN_INFO "PM: Device %s not prepared "

                                "for power transition: code %d\n",

                                dev_name(dev), error);

                        put_device(dev); //引用记数减一

                        break;

                }

                dev->power.is_prepared = true;//设备正准备着进入省电模式

                if (!list_empty(&dev->power.entry))

                        list_move_tail(&dev->power.entry, &dpm_prepared_list);

                //如果dev->power.entry不是空,把dev->power.entry加入dpm_prepared_list

                put_device(dev); //引用记数减一

        }

        mutex_unlock(&dpm_list_mtx);

        return error;

}

 

static int device_prepare(struct device *dev, pm_message_t state)

{

        int error = 0;

 

        device_lock(dev);

 

        dev->power.wakeup_path = device_may_wakeup(dev);//获得设备的唤醒能力,wakeup_path是个bool量,不要被名字骗了。

        /*

        下面的过程一起说,首先pm_dev_dbgsuspend_report_result是调试打印

        prepare的调用顺序

        dev->pm_domain-> ops.prepare()

        dev->type->pm->prepare()

        dev->class->pm->prepare()

dev->bus->pm->prepare()

前提是都存在

        */

        if (dev->pm_domain) {

                pm_dev_dbg(dev, state, "preparing power domain ");

                if (dev->pm_domain->ops.prepare)

                        error = dev->pm_domain->ops.prepare(dev);

                suspend_report_result(dev->pm_domain->ops.prepare, error);

                if (error)

                        goto End;

        } else if (dev->type && dev->type->pm) {

                pm_dev_dbg(dev, state, "preparing type ");

                if (dev->type->pm->prepare)

                        error = dev->type->pm->prepare(dev);

                suspend_report_result(dev->type->pm->prepare, error);

                if (error)

                        goto End;

        } else if (dev->class && dev->class->pm) {

                pm_dev_dbg(dev, state, "preparing class ");

                if (dev->class->pm->prepare)

                        error = dev->class->pm->prepare(dev);

                suspend_report_result(dev->class->pm->prepare, error);

                if (error)

                        goto End;

        } else if (dev->bus && dev->bus->pm) {

                pm_dev_dbg(dev, state, "preparing ");

                if (dev->bus->pm->prepare)

                        error = dev->bus->pm->prepare(dev);

                suspend_report_result(dev->bus->pm->prepare, error);

        }

 

 End:

        device_unlock(dev);

 

        return error;

}

suspend阶段由suspend回调实现,它停止设备的一切I/O操作。它同时也可以保存设备的寄存器,依据设备所属的总线类型,让设备进入合适的低功耗状态,同时可以使能唤醒事件。

int dpm_suspend(pm_message_t state)

{

        ktime_t starttime = ktime_get();

        int error = 0;

 

        might_sleep();//为能睡眠的函数注释,需要编译选项

 

        mutex_lock(&dpm_list_mtx);

        pm_transition = state;//statePMSG_SUSPEND

        async_error = 0;

        while (!list_empty(&dpm_prepared_list)) {

                struct device *dev = to_device(dpm_prepared_list.prev);

 

                get_device(dev);

                mutex_unlock(&dpm_list_mtx);

                //上面看dpm_prepare()

                error = device_suspend(dev);//看下面

 

                mutex_lock(&dpm_list_mtx);

                if (error) {

                        pm_dev_err(dev, state, "", error);

                        dpm_save_failed_dev(dev_name(dev));

                        put_device(dev);

                        break;

                }

                if (!list_empty(&dev->power.entry))

                        list_move(&dev->power.entry, &dpm_suspended_list);

               //如果dev->power.entry不是空,把dev->power.entry加入dpm_suspended_list

                put_device(dev);

                if (async_error)

                        break;

        }

        mutex_unlock(&dpm_list_mtx);

        async_synchronize_full();//同步系统所有的异步函数,等待这些异步函数执行完。

        if (!error)

                error = async_error;

        if (error) {

                suspend_stats.failed_suspend++;

                dpm_save_failed_step(SUSPEND_SUSPEND);

        } else

                dpm_show_time(starttime, state, NULL);//显示时间。

        return error;

}

 

先简单介绍一个叫pm_op()的函数,它会根据state.event执行对应的函数,有很多的事件,现在我们只看这些

static int pm_op(struct device *dev,

                 const struct dev_pm_ops *ops,

                 pm_message_t state)

{

        …

        switch (state.event) {

#ifdef CONFIG_SUSPEND

        case PM_EVENT_SUSPEND:

                if (ops->suspend) {

                        error = ops->suspend(dev);//调用suspend()

                        suspend_report_result(ops->suspend, error);//调试打印

                }

                break;

        case PM_EVENT_RESUME:

                if (ops->resume) {

                        error = ops->resume(dev);//调用resume ()

                        suspend_report_result(ops->resume, error); //调试打印

                }

                break;

#endif /* CONFIG_SUSPEND */

          …

}

__device_suspend

static int __device_suspend(struct device *dev, pm_message_t state, bool async)

{

        int error = 0;

 

        dpm_wait_for_children(dev, async);

/*

等待所有的孩子节点的pm操作完成,这里可以体现suspend是从叶子节点开始的。前提是使用了异步的,有另一个叫async_suspend()的过程会让async=1,我们的过程不会等

*/

 

        if (async_error)//下面会看到赋值

                return 0;

        //runtime简单说一下,这种模型允许设备在系统运行阶段进入低功耗状态,原则上,他可以独立于其他的电源管理活动。

        pm_runtime_get_noresume(dev);

        if (pm_runtime_barrier(dev) && device_may_wakeup(dev))

                pm_wakeup_event(dev, 0);

 

        if (pm_wakeup_pending()) {//检查是否要取消suspend

                pm_runtime_put_sync(dev);

                async_error = -EBUSY;

                return 0;

        }

 

        device_lock(dev);

        /*

        下面和prepare差不多,就是个顺序问题。

        */

        if (dev->pm_domain) {

                pm_dev_dbg(dev, state, "power domain ");

                error = pm_op(dev, &dev->pm_domain->ops, state);

                goto End;

        }

 

        if (dev->type && dev->type->pm) {

                pm_dev_dbg(dev, state, "type ");

                error = pm_op(dev, dev->type->pm, state);

                goto End;

        }

 

        //下面看到判断pmsuspend,这是新老电源管理方式的判断

        if (dev->class) {

                if (dev->class->pm) {

                        pm_dev_dbg(dev, state, "class ");

                        error = pm_op(dev, dev->class->pm, state);

                        goto End;

                } else if (dev->class->suspend) {

                        pm_dev_dbg(dev, state, "legacy class ");

                        error = legacy_suspend(dev, state, dev->class->suspend);

                        goto End;

                }

        }

 

        if (dev->bus) {

                if (dev->bus->pm) {

                        pm_dev_dbg(dev, state, "");

                        error = pm_op(dev, dev->bus->pm, state);

                } else if (dev->bus->suspend) {

                        pm_dev_dbg(dev, state, "legacy ");

                        error = legacy_suspend(dev, state, dev->bus->suspend);

                }

        }

 

 End:

        if (!error) {

                dev->power.is_suspended = true;//设备被挂起

                if (dev->power.wakeup_path

                    && dev->parent && !dev->parent->power.ignore_children)

                        dev->parent->power.wakeup_path = true;

        }

 

        device_unlock(dev);

        complete_all(&dev->power.completion);//报告完成,还记得我们之前调用dpm_wait_for_children(dev, async);等孩子们都完成,它就等待这个完成量

 

        if (error) {

                pm_runtime_put_sync(dev);

                async_error = error;

        } else if (dev->power.is_suspended) {

                __pm_runtime_disable(dev, false);

        }

 

        return error;

}

 

下面是suspend_enter()

小总结一下,上面我们看到了关进程、调用busclassdevice的相关回调,下面是调用平台的相关回调、noirqsuspend调用、关闭cpu、系统核suspend调用,顺便提一下我们现在又回到了kernel/power/suspend.c

//里面有suspend_test是调试用的,有了它可以不真进入suspend,只是观察这个过程。我的例子不用

static int suspend_enter(suspend_state_t state, bool *wakeup)

{

        int error;

        //suspend_ops是平台的,我在下面详细说

        if (suspend_ops->prepare) {

                error = suspend_ops->prepare();

                if (error)

                        goto Platform_finish;

        }

 

        error = dpm_suspend_noirq(PMSG_SUSPEND);

/*

我们上面的suspend最终调用的是ops->suspend,这个会调用ops->suspend_noirq。过程差不多。

suspend_noirq阶段发生在IRQ被禁止之后,这意味着该回调运行期间,驱动程序的中断处理代码不会被调用。回调方法可以保存上一阶段没有保存的寄存器并最终让设备进入相应的低功耗状态。

*/

        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();

                if (error)

                        goto Platform_wake;

        }

 

        if (suspend_test(TEST_PLATFORM))

                goto Platform_wake;

 

        error = disable_nonboot_cpus();//对与smp,它还要卸掉所有不能直接启动的cpu

        if (error || suspend_test(TEST_CPUS))

                goto Enable_cpus;

 

        arch_suspend_disable_irqs();//中断禁止

        BUG_ON(!irqs_disabled());

 

        error = syscore_suspend();

/*suspend_enter()会调用syscore_suspend(),会调用所有注册系统核suspend的回调

先说一下,调用的调试信息还受到

                       if (initcall_debug)

                               pr_info("PM: Calling %pF\n", ops->suspend);

initcall_debug的控制,赋1方法:

echo 1 > /sys/module/kernel/parameters/initcall_debug

 

本人的平台

static struct sleep_save s3c244x_sleep[] = {

       SAVE_ITEM(S3C2440_DSC0),

       SAVE_ITEM(S3C2440_DSC1),

       SAVE_ITEM(S3C2440_GPJDAT),

       SAVE_ITEM(S3C2440_GPJCON),

       SAVE_ITEM(S3C2440_GPJUP)

};

 

static int s3c244x_suspend(void)

{

       s3c_pm_do_save(s3c244x_sleep, ARRAY_SIZE(s3c244x_sleep));

       return 0;

}

 

static void s3c244x_resume(void)

{

       s3c_pm_do_restore(s3c244x_sleep, ARRAY_SIZE(s3c244x_sleep));

}

 

struct syscore_ops s3c244x_pm_syscore_ops = {

       .suspend        = s3c244x_suspend,

       .resume         = s3c244x_resume,

};

就是suspend时把s3c244x_sleep中的寄存器值保存起来,resume再写进去。

*/

 

        if (!error) {

                *wakeup = pm_wakeup_pending();//判断睡眠是否继续

                if (!(suspend_test(TEST_CORE) || *wakeup)) {

                        error = suspend_ops->enter(state);

/*

到这我们的系统已睡了,下面就是resume,对与resume我不一步一步的分析。

我只说它和suspend的过程相反。

*/

                        events_check_enabled = false;

                }

                syscore_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_noirq(PMSG_RESUME);

 

 Platform_finish:

        if (suspend_ops->finish)

                suspend_ops->finish();

 

        return error;

}

原创粉丝点击