蜗窝科技

Linux电源管理(6)_Generic PM之Suspend功能

wowo · · 电源管理子系统 · 阅读 150701 · 2014年8月存档

1. 前言

Linux内核提供了三种Suspend: Freeze、Standby和STR(Suspend to RAM),在用户空间向”/sys/power/state”文件分别写入”freeze”、”standby”和”mem”,即可触发它们。

内核中,Suspend及Resume过程涉及到PM Core、Device PM、各个设备的驱动、Platform dependent PM、CPU control等多个模块,涉及了console switch、process freeze、CPU hotplug、wakeup处理等过个知识点。就让我们跟着内核代码,一一见识它们吧。

2. Suspend功能有关的代码分布

内核中Suspend功能有关的代码包括PM core、Device PM、Platform PM等几大块,具体如下:

1)PM Core

kernel/power/main.c—-提供用户空间接口(/sys/power/state)

kernel/power/suspend.c—-Suspend功能的主逻辑

kernel/power/suspend_test.c—-Suspend功能的测试逻辑

kernel/power/console.c—-Suspend过程中对控制台的处理逻辑

kernel/power/process.c—-Suspend过程中对进程的处理逻辑

2)Device PM

drivers/base/power/*—-具体可参考“Linux电源管理(4)_Power Management Interface”的描述。

设备驱动—-具体设备驱动的位置,不再涉及。

3)Platform dependent PM

include/linux/suspend.h—-定义platform dependent PM有关的操作函数集

arch/xxx/mach-xxx/xxx.c或者

arch/xxx/plat-xxx/xxx.c—-平台相关的电源管理操作

3. suspend&resume过程概述

下面图片对Linux suspend&resume过程做了一个概述,读者可以顺着这个流程阅读内核源代码。具体的说明,可以参考后面的代码分析。

suspend_flow

4. 代码分析

4.1 suspend入口

在用户空间执行如下操作:

echo “freeze” > /sys/power/state

echo “standby” > /sys/power/state

echo “mem” > /sys/power/state

会通过sysfs触发suspend的执行,相应的处理代码如下:

static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
                           const char *buf, size_t n)
{
        suspend_state_t state;
        int error;

        error = pm_autosleep_lock();
        if (error)
                return error;

        if (pm_autosleep_state() > PM_SUSPEND_ON) {
                error = -EBUSY;
                goto out;
        }

        state = decode_state(buf, n);
        if (state < PM_SUSPEND_MAX)
                error = pm_suspend(state);
        else if (state == PM_SUSPEND_MAX)
                error = hibernate();
        else
                error = -EINVAL;

 out:
        pm_autosleep_unlock();
        return error ? error : n;
}

power_attr(state);

power_attr定义了一个名称为state的attribute文件,该文件的store接口为state_store,该接口在lock住autosleep功能后,解析用户传入的buffer(freeze、standby or mem),转换成state参数。

state参数的类型为suspend_state_t,在include\linux\suspend.h中定义,为电源管理状态在内核中的表示。具体如下:

typedef int __bitwise suspend_state_t;

#define PM_SUSPEND_ON           ((__force suspend_state_t) 0)
#define PM_SUSPEND_FREEZE       ((__force suspend_state_t) 1)
#define PM_SUSPEND_STANDBY      ((__force suspend_state_t) 2)
#define PM_SUSPEND_MEM          ((__force suspend_state_t) 3)
#define PM_SUSPEND_MIN          PM_SUSPEND_FREEZE
#define PM_SUSPEND_MAX          ((__force suspend_state_t) 4)

根据state的值,如果不是(PM_SUSPEND_MAX,对应hibernate功能),则调用pm_suspend接口,进行后续的处理。

pm_suspend在kernel/power/suspend.c定义,处理所有的suspend过程。

4.2 pm_suspend & enter_state

pm_suspend的实现非常简单,简单的做一下参数合法性判断,直接调用enter_state接口,如下:

int pm_suspend(suspend_state_t state)
{
        int error;

        if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
                return -EINVAL;

        error = enter_state(state);
        if (error) {
                suspend_stats.fail++;
                dpm_save_failed_errno(error);
        } else {
                suspend_stats.success++;
        }
        return error;
}

enter_state代码为:

static int enter_state(suspend_state_t state)
{
        int error;

        if (!valid_state(state))
                return -ENODEV;

        if (!mutex_trylock(&pm_mutex))
                return -EBUSY;

        if (state == PM_SUSPEND_FREEZE)
                freeze_begin();

        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(state);
        if (error)
                goto Unlock;

        if (suspend_test(TEST_FREEZER))
                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;
}

主要工作包括:

a)调用valid_state,判断该平台是否支持该电源状态。

suspend的最终目的,是让系统进入可恢复的挂起状态,而该功能必须有平台相关代码的参与才能完成,因此内核PM Core就提供了一系列的回调函数(封装在platform_suspend_ops中),让平台代码(如arch/arm/mach-xxx/pm.c)实现,然后由PM Core在合适的时机调用。这些回调函数包含一个valid函数,就是用来告知PM Core,支持哪些state。

最后看一下valid_state的实现(删除了无关代码):

bool valid_state(suspend_state_t state)
{
        if (state == PM_SUSPEND_FREEZE) {
                return true;
        }
        /*
         * PM_SUSPEND_STANDBY and PM_SUSPEND_MEMORY states need lowlevel
         * support and need to be valid to the lowlevel
         * implementation, no valid callback implies that none are valid.
         */
        return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
}

如果是freeze,无需平台代码参与即可支持,直接返回true。对于standby和mem,则需要调用suspend_ops的valid回掉,由底层平台代码判断是否支持。

b)加互斥锁,只允许一个实例处理suspend。

c)如果state是freeze,调用freeze_begin,进行suspend to freeze相关的特殊动作。我会在后面统一分析freeze的特殊动作,这里暂不描述。

d)打印提示信息,同步文件系统。

e)调用suspend_prepare,进行suspend前的准备,主要包括switch console和process&thread freezing。如果失败,则终止suspend过程。

f)然后,调用suspend_devices_and_enter接口,该接口负责suspend和resume的所有实际动作。前半部分,suspend console、suspend device、关中断、调用平台相关的suspend_ops使系统进入低功耗状态。后半部分,在系统被事件唤醒后,处理相关动作,调用平台相关的suspend_ops恢复系统、开中断、resume device、resume console。

g)最后,调用suspend_finish,恢复(或等待恢复)process&thread,还原console。

4.3 suspend_prepare

suspend_prepare的代码如下:

static int suspend_prepare(suspend_state_t state)
{
        int error;

        if (need_suspend_ops(state) && (!suspend_ops || !suspend_ops->enter))
                return -EPERM;

        pm_prepare_console();

        error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
        if (error)
                goto Finish;

        error = suspend_freeze_processes();
        if (!error)
                return 0;

        suspend_stats.failed_freeze++;
        dpm_save_failed_step(SUSPEND_FREEZE);
 Finish:
        pm_notifier_call_chain(PM_POST_SUSPEND);
        pm_restore_console();
        return error;
}

主要工作为:

a)检查suspend_ops是否提供了.enter回调,没有的话,返回错误。

b)调用pm_prepare_console,将当前console切换到一个虚拟console并重定向内核的kmsg(需要的话)。该功能称作VT switch,后面我会在稍微详细的介绍一下,但Linux控制台子系统是相当复杂的,更具体的分析,要在控制台子系统的分析文章中说明。

c)调用pm_notifier_call_chain,发送suspend开始的消息(PM_SUSPEND_PREPARE),后面会详细描述。

d)调用suspend_freeze_processes,freeze用户空间进程和一些内核线程。该功能称作freezing-of-tasks,我会专门用一篇文章去分析它。本文就不再详细说明了。

e)如果freezing-of-tasks失败,调用pm_restore_console,将console切换回原来的console,并返回错误,以便能终止suspend。

4.4 suspend_devices_and_enter

suspend_devices_and_enter的过程较为复杂,代码实现如下:

int suspend_devices_and_enter(suspend_state_t state)
{
        int error;
        bool wakeup = false;

        if (need_suspend_ops(state) && !suspend_ops)
                return -ENOSYS;

        trace_machine_suspend(state);
        if (need_suspend_ops(state) && suspend_ops->begin) {
                error = suspend_ops->begin(state);
                if (error)
                        goto Close;
        }
        suspend_console();
        ftrace_stop();
        suspend_test_start();
        error = dpm_suspend_start(PMSG_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 && need_suspend_ops(state)
                && 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 (need_suspend_ops(state) && suspend_ops->end)
                suspend_ops->end();
        trace_machine_suspend(PWR_EVENT_EXIT);
        return error;

 Recover_platform:
        if (need_suspend_ops(state) && suspend_ops->recover)
                suspend_ops->recover();
        goto Resume_devices;
}

a)再次检查平台代码是否需要提供以及是否提供了suspend_ops。

b)调用suspend_ops的begin回调(有的话),通知平台代码,以便让其作相应的处理(需要的话)。可能失败,需要跳至Close处执行恢复操作(suspend_ops->end)。

c)调用suspend_console,挂起console。该接口由"kernel\printk.c"实现,主要是hold住一个lock,该lock会阻止其它代码访问console。

d)调用ftrace_stop,停止ftrace功能。ftrace是一个很有意思的功能,后面再介绍。

e)调用dpm_suspend_start,调用所有设备的->prepare和->suspend回调函数(具体可参考“Linux电源管理(4)_Power Management Interface”的描述),suspend需要正常suspend的设备。suspend device可能失败,需要跳至 Recover_platform,执行recover操作(suspend_ops->recover)。

f)以上都是suspend前的准备工作,此时,调用suspend_enter接口,使系统进入指定的电源状态。该接口的内容如下:

static int suspend_enter(suspend_state_t state, bool *wakeup)
{
        int error;

        if (need_suspend_ops(state) && 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 (need_suspend_ops(state) && suspend_ops->prepare_late) {
                error = suspend_ops->prepare_late();
                if (error)
                        goto Platform_wake;
        }

        if (suspend_test(TEST_PLATFORM))
                goto Platform_wake;

        /*
         * PM_SUSPEND_FREEZE equals
         * frozen processes + suspended devices + idle processors.
         * Thus we should invoke freeze_enter() soon after
         * all the devices are suspended.
         */
        if (state == PM_SUSPEND_FREEZE) {
                freeze_enter();
                goto Platform_wake;
        }

        error = disable_nonboot_cpus();
        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();
        }

        arch_suspend_enable_irqs();
        BUG_ON(irqs_disabled());

 Enable_cpus:
        enable_nonboot_cpus();

 Platform_wake:
        if (need_suspend_ops(state) && suspend_ops->wake)
                suspend_ops->wake();

        dpm_resume_start(PMSG_RESUME);

 Platform_finish:
        if (need_suspend_ops(state) && suspend_ops->finish)
                suspend_ops->finish();

        return error;
}

f1)该接口处理完后,会通过返回值告知是否enter成功,同时通过wakeup指针,告知调用者,是否有wakeup事件发生,导致电源状态切换失败。

f2)调用suspend_ops的prepare回调(有的话),通知平台代码,以便让其在即将进行状态切换之时,再做一些处理(需要的话)。该回调可能失败(平台代码出现意外),失败的话,需要跳至Platform_finish处,调用suspend_ops的finish回调,执行恢复操作。

f3)调用dpm_suspend_end,调用所有设备的->suspend_late和->suspend_noirq回调函数(具体可参考“Linux电源管理(4)_Power Management Interface”的描述),suspend late suspend设备和需要在关中断下suspend的设备。需要说明的是,这里的noirq,是通过禁止所有的中断线的形式,而不是通过关全局中断的方式。同样,该操作可能会失败,失败的话,跳至Platform_finish处,执行恢复动作。

f4)调用suspend_ops的prepare_late回调(有的话),通知平台代码,以便让其在最后关头,再做一些处理(需要的话)。该回调可能失败(平台代码出现意外),失败的话,需要跳至Platform_wake处,调用suspend_ops的wake回调,执行device的resume、调用suspend_ops的finish回调,执行恢复操作。

f5)如果是suspend to freeze,执行相应的操作,包括冻结进程、suspended devices(参数为PM_SUSPEND_FREEZE)、cpu进入idle。如果有任何事件使CPU从idle状态退出,跳至Platform_wake处,执行wake操作。

f6)调用disable_nonboot_cpus,禁止所有的非boot cpu。也会失败,执行恢复操作即可。

f7)调用arch_suspend_disable_irqs,关全局中断。如果无法关闭,则为bug。

f8)调用syscore_suspend,suspend system core。同样会失败,执行恢复操作即可。有关syscore,我会在另一篇文章中详细描述。

f9)如果很幸运,以上操作都成功了,那么,切换吧。不过,别高兴太早,还得调用pm_wakeup_pending检查一下,这段时间内,是否有唤醒事件发生,如果有就要终止suspend。

f10)如果一切顺利,调用suspend_ops的enter回调,进行状态切换。这时,系统应该已经suspend了……

f11)suspend过程中,唤醒事件发生,系统唤醒,该函数接着执行resume动作,并最终返回。resume动作基本上是suspend的反动作,就不再继续分析了。

f12)或者,由于意外,suspend终止,该函数也会返回。

g)suspend_enter返回,如果返回原因不是发生错误,且不是wakeup事件。则调用suspend_ops的suspend_again回调,检查是否需要再次suspend。再什么情况下要再次suspend呢?需要看具体的平台了,谁知道呢。

h)继续resume操作,resume device、start ftrace、resume console、suspend_ops->end等等。

i)该函数返回后,表示系统已经resume。

4.5 suspend_finish

比较简单:

static void suspend_finish(void)
{
        suspend_thaw_processes();
        pm_notifier_call_chain(PM_POST_SUSPEND);
        pm_restore_console();
}

a)恢复所有的用户空间进程和内核线程。

b)发送suspend结束的通知。

c)将console切换回原来的。

5. 重要知识点回顾

5.1 VT switch

通常情况下,系统控制台模块(drivers\tty\vt\)会在suspend的过程中,重新分配一个console,并将控制台切换到该console上。然后在resume时,切换回旧的console。这就是VT switch功能。VT switch是很耗时的,因此内核提供了一些机制,控制是否使用这个功能:

1)提供一个接口函数pm_set_vt_switch(drivers\tty\vt\vt_ioctl.c),方便其它内核模块从整体上关闭或者开启VT switch功能。

2)VT switch全局开关处于开启状态时,满足如下的一种条件(可参考kernel\power\console.c相关的描述),即会使能VT switch

a)有console driver调用pm_vt_switch_required接口,显式的要求使能VT switch。PM core的console模块会把这些信息记录在一个名称为pm_vt_switch_list的链表中。

b)系统禁止在suspend的过程中suspend console(由kernel/printk.c中的console_suspend_enabled变量控制)。很有可能需要使用console查看suspend过程,此时为了使console不混乱,有必要进行VT switch。

c)没有任何console driver关心是否需要VT switch,换句话说没有任何driver调用pm_vt_switch_required接口要求使能或禁止VT switch功能。此时会按照旧的习惯,进行VT switch。

因此,suspend过程对console的处理分为4步:

prepare console:负责在需要VT swich时,将当前console切换到SUSPEND console。

int pm_prepare_console(void)
{
        if (!pm_vt_switch())
                return 0;

        orig_fgconsole = vt_move_to_console(SUSPEND_CONSOLE, 1);
        if (orig_fgconsole < 0)
                return 1;

        orig_kmsg = vt_kmsg_redirect(SUSPEND_CONSOLE);
        return 0;
}

suspend console:挂起console,由kernel/printk.c实现,主要是hold住console用的互斥锁,使他人无法使用console。

resume console:对console解锁。

restore console:将console恢复为初始的console。

void pm_restore_console(void)
{
        if (!pm_vt_switch())
                return;

        if (orig_fgconsole >= 0) {
                vt_move_to_console(orig_fgconsole, 0);
                vt_kmsg_redirect(orig_kmsg);
        }
}

也许,您会问,why VT switch?先留着这个疑问吧,等到分析控制台时再回答。

5.2 freezing of task

进程的freezing功能,是suspend、hibernate等电源管理功能的组成部分,在新版本内核中,它被独立出来,作为一个独立的电源管理状态(freeze)。该功能的目的,是在电源管理的状态切换过程中,确保所有用户空间进程和部分内核线程处于一个稳定的状态。有关该功能的具体描述,请参考wowotech后续的文章。

5.3 PM notifier

PM notifier是基于内核blocking notifier功能实现的。blocking notifier提供了一种kernel内部的消息通知机制,消息接受者通过notifier注册的方式,注册一个回调函数,关注消息发送者发出的notifier。当消息产生时,消息产生者通过调用回调函数的形式,通知消息接受者。这种调用,是可以被阻塞的,因此称作blocking notifier。

那suspend功能为什么使用notifier呢?原因可能有多种,这里我举一个例子,这是我们日常开发中可能会遇到的。

由之前的描述可知,suspend过程中,suspend device发生在进程被freeze之后,resume device发生在进程被恢复之前。那么:

1)如果有些设备就需要在freeze进程之前suspend怎么办?

2)如果有些设备的resume动作需要较多延时,或者要等待什么事情发生,那么如果它的resume动作发生在进程恢复之前,岂不是要阻止所有进程的恢复?更甚者,如果该设备要等待某个进程的数据才能resume,怎么办?

再来看suspend_prepare和suspend_finish中的处理:

static int suspend_prepare(suspend_state_t state) {
…
        error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
        if (error)
                goto Finish;

        error = suspend_freeze_processes();
        …
}

static void suspend_finish(void)
{
        suspend_thaw_processes();
        pm_notifier_call_chain(PM_POST_SUSPEND);
        pm_restore_console();
}

原来PM notifier是在设备模型的框架外,开了一个后门,那些比较特殊的driver,可以绕过设备模型,直接接收PM发送的suspend信息,以便执行自身的suspend动作。特别是resume时,可以在其它进程都正好工作的时候,只让suspend进程等待driver的resume。

感兴趣的读者,可以围观一下下面这个活生生的例子(顺便提一下,好的设计是不应该有例外的):

drivers\video\omap2\dss\core.c

5.4 device PM ops 和platform PM ops的调用时机

对Linux驱动工程师来说,device PM ops和platform PM ops就是电源管理(suspend)的全部,只要在合适的地方,实现合适的回调函数,即可实现系统的电源管理。但现实太复杂了,以至于kernel提供的这两个数据结构也很复杂,再回忆一下,如下:

struct dev_pm_ops {
        int (*prepare)(struct device *dev);
        void (*complete)(struct device *dev);
        int (*suspend)(struct device *dev);
        int (*resume)(struct device *dev);
        int (*freeze)(struct device *dev);
        int (*thaw)(struct device *dev);
        int (*poweroff)(struct device *dev);
        int (*restore)(struct device *dev);
        int (*suspend_late)(struct device *dev);
        int (*resume_early)(struct device *dev);
        int (*freeze_late)(struct device *dev);
        int (*thaw_early)(struct device *dev);
        int (*poweroff_late)(struct device *dev);
        int (*restore_early)(struct device *dev);
        int (*suspend_noirq)(struct device *dev);
        int (*resume_noirq)(struct device *dev);
        int (*freeze_noirq)(struct device *dev);
        int (*thaw_noirq)(struct device *dev);
        int (*poweroff_noirq)(struct device *dev);
        int (*restore_noirq)(struct device *dev);
        int (*runtime_suspend)(struct device *dev);
        int (*runtime_resume)(struct device *dev);
        int (*runtime_idle)(struct device *dev);
};

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);
        bool (*suspend_again)(void);
        void (*end)(void);
        void (*recover)(void);
};

虽然内核的注释已经相当详细了,但我们一定会犯晕,到底该实现哪些回调?这些回调的应用场景又是什么?蜗蜗以为,要熟练使用这些回调,唯一的方法就是多coding、多理解。除此之外,我们可以总结一下在电源状态切换时,这些回调的调用时机,从侧面帮助理解。如下(只介绍和suspend功能有关的,struct dev_pm_ops简称D,struct platform_suspend_ops简称P):

pm_ops_flow

5.5 suspend过程的同步和****PM wakeup

最重要的事情,如果suspend的过程中,有唤醒事件产生怎么办?正常的流程,应该终止suspend,返回并处理事件。但由于suspend过程的特殊性,进程被freeze、关中断等等,导致事情并没有那么简单,以至于在很久的一段时间内,kernel都不能很好的处理。这也称作suspend过程的同步问题。

在美好的旧时光里,suspend大多用于热关机,因此同步问题的影响并不突出(因为操作并不频繁)。但来到新时代之后,事情变了,Android竟然用suspend作日常的待机(操作就相当频繁了),这时问题就大了。那怎么解决呢?得靠system wakeup framework,也就是suspend过程中所调用的pm_wakeup_pending接口所在的模块。我会在下一篇文章中继续该模块的分析,这里就不再继续了。

原创文章,转发请注明出处。蜗窝科技,www.wowotech.net。

标签:Linux内核suspend电源管理resume

继续讨论

新讨论请用 GitHub 账号在下方发言——评论会保存在本仓库的 Discussions 里,与站点同寿; 再下方的「历史评论」是原站的只读存档。

评论(121 条,只读存档)

以下为原站的历史评论(只读存档);继续讨论请用上方的评论区。

  • linuxer

    骚年,终于回归了
  • wowo

    哈哈,几天不见你用词洋气了啊。每天晚上写一点,但愿能积少成多
    • linuxer

      @wowo:加油,我们建立“华南区linux首席团队ZH分部”的任务就交给你啦,^_^
      • wowo

        @linuxer:哈哈,保证不辱使命。
  • 动漫资讯 主页

    博主首页会跳的东西不错哈
    • wowo

      @动漫资讯:晕啊,有广告了,免费空间不靠谱啊
  • Jamie

    查詢pm_wakeup_pending時,看見本blog 超棒! 期待你更詳細說明此模塊的文章
    • wowo

      @Jamie:多謝您的表揚。我會儘快發出system wakeup framework的分析文章。
  • 海

    分析的很详细,支持一下。
  • 醉中幻想

    看韦东山的视频给了这个链接,写得实在太好了。
    • wowo

      @醉中幻想:多谢夸奖。
  • Xiang

    Hi 有个问题想请教 state_store()这个函式里,有个判断式 if (pm_autosleep_state() > PM_SUSPEND_ON) { error = -EBUSY; goto out; } 看起来是想autosleep有启动的时候,让写入sysfs进入suspend的功能失效 为何要这么设计呢? 难道不能两个共存?或是这是另一个未解的问题,所以暂时这么做。
    • wowo

      @Xiang:参考autosleep中的try_to_suspend函数(/pm_subsystem/autosleep.html)可知,如果autosleep被enable,会在系统没有active的wakeup event时,suspend系统。这个功能和用write state的方式调用suspend得到的效果是完全一样的。 因此当autosleep有效时,就没必要使用write state的方式再调用一次。 不过总有例外,如果真的需要怎么办?从另一个角度看,autosleep是谁enable的?是用户程序,那么用户程序在write state之前,有责任把autosleep disable掉。 而对kernel而言,就没有必要花费更多的逻辑去兼容这两种操作了。
  • Tekkaman Ninja

    1)PM Core kernel/power/* ??
    • wowo

      @Tekkaman Ninja:非常感谢,已经一一修正。能有您这样的读者,真的很感激!! PS:不吹毛求疵的程序员不是好程序员啊!大家共勉!
  • shoujixiaodao

    5.4的图示,p.valid和p.begian之间的 p.enter 修改成check p.enter exist是否更很合适? p.enter是soc自己实现的最终的sleep,这个地方仅仅是检测:如果 需要p.enter但是并没有实现soc的p.enter则报错返回。多谢!
    • wowo

      @shoujixiaodao:非常感谢,这个地方应该是笔误,应该把它删掉就是了。这个图愿意是描述一下过程,check这种步骤,没有打算画出来。等等回改正。 谢谢~~
  • shoujixiaodao

    蜗窝,linuxer:enter_state()函数中的如下部分。有没有遇到过sys_sync()会执行很长很长时间。 printk(KERN_INFO "PM: Syncing filesystems ... "); sys_sync(); printk("done.\n"); 我这遇到过会执行三四十秒。当然概率非常非常的低。 我用规定时间比如2s判断sys_sync还无法完成则goto到unlock来规避。但是没有从根本上解决。多谢!
    • wowo

      @shoujixiaodao:个人意见,可以顺着sys_sync去查,把根本原因查出来是有必要的。肯定哪里有不规范的实现。可以重点看看底层存储设备的驱动,如NAND等(如果存储设备是nand的话)。
  • hini

    大神,咨询个问题,从android 5.0代码来看。libsuspend.so里面移除了autosleep,只能wakeup_count来做休眠功能,给 //sys/power/wakeup_count 写一个mem 来休眠。 wakeup_count 不是做suspend同步的么。也可以做休眠么。
    • wowo

      @hini:不会的,要么您看错了,要么你们的team改了原生代码。
      • andy

        @wowo:貌似真的屏蔽掉了
  • xiao-coding

    @wowo: 看您的blog,有这么一段话 “f10)如果一切顺利,调用suspend_ops的enter回调,进行状态切换。这时,系统应该已经suspend了……” , 我的疑问是如果这时候有中断发生,那么这个中断是怎么处理的? 然后如何继续调用 syscore_resume()等恢复系统的?
    • xiao-coding

      @xiao-coding:我在suspend_enter()函数里加了log,但是总是抓不到系统何时调用的syscore_resume()函数?
      • wowo

        @xiao-coding:能抓到执行“error = suspend_ops->enter(state);”吗?例如在这之前打印?
        • xiao-coding

          @wowo:可以的
          • xiao-coding

            @xiao-coding:我想请教您的是,从产生中断到执行syscore_resume()这之间的流程是怎么样的?
            • wowo

              @xiao-coding:这依赖于具体的平台实现( suspend_ops->enter),你可以把enter回调的代码贴一下。
              • @wowo

                @wowo:找了好长时间, suspend_ops->enter这个函数指针,没有找到在哪里赋值的?
              • xiao-coding

                @wowo:@wowo: 这个enter函数里到底做了些什么?
  • wowo

    @xiao-coding: 以omap平台为例,suspend_ops->enter位于“arch/arm/mach-omap2/pm.c”中: static int omap_pm_enter(suspend_state_t suspend_state) { int ret = 0; if (!omap_pm_suspend) return -ENOENT; /* XXX doublecheck */ switch (suspend_state) { case PM_SUSPEND_STANDBY: case PM_SUSPEND_MEM: ret = omap_pm_suspend(); break; default: ret = -EINVAL; } return ret; } “arch/arm/mach-omap2/pm44xx.c”等文件中,有对omap_pm_suspend赋值,最终调用的函数为omap4_pm_suspend等等。 你可以按照最这个过程在你所用的平台上找一下。具体做什么事情,和平台有关,我没办法告诉你。
    • xiao-coding

      @wowo:谢谢您的回答
  • qinghu

    你好,我向你提问的问题,还有你的答复,怎么在你的评论里找不到了呢? 您删除了吗>
  • koala

    这里的noirq,是通过禁止所有的中断线的形式,而不是通过关全局中断的方式。 -------------------------- wowo,请教一下,上面一句话怎么理解?
    • wowo

      @koala:对ARM(或者ARM中断控制器)来说,可以单独开关每个设备的中断(即控制device到GIC之间的路径),也可以开关全局中断(即GIC到CPU的路径)。这里指的是前一种。
      • koala

        @wowo:当dpm_suspend_noirq之后,所有外设的中断线被关闭。假如此时外设来一个中断,由于irq被disable了,CPU无法响应,irq_handler就不会被执行,那就是说此时这个wakeup source是无法阻止系统休眠的?即使后面有pm_wakeup_pending的检查,因为这个中断无法送达GIC。 wowo,不知道哪边理解的不正确?
        • wowo

          @koala:首先,suspend noirq的时候,确实会调用suspend_device_irqs disable所有设备的中断线,但有例外,就是那些具有IRQF_NO_SUSPEND(request IRQ是提供的)标志的设备,具体可参考如下的调用逻辑:suspend_enter-->dpm_suspend_end-->dpm_suspend_noirq-->suspend_device_irqs-->__disable_irq。 另外,suspend noirq之后,紧接着就会关全局中断,之后才会调用pm_wakeup_pending检查pending状态,它检查的,是suspend noirq之前以及之后的所有wakeup event。 最后,全局中断被关闭之后,就只能依赖suspend enter时的由CPU自行检查了,如果这一段时间内,有中断产生,enter会立即返回。
          • koala

            @wowo:比如通话时,proximity靠近时灭屏,系统suspend。然后远离后Proximity的irq又能唤醒系统。虽然Proximity的中断线没有关闭,但是全局中断在suspend之前关闭了啊,为啥proximity的irq能在suspend之后唤醒系统呢?
            • wowo

              @koala:这个估计要linuxer同学给我们解释一下,我的理解是:关全局中断,意味着CPU不再处理(handler)GIC送来的中断,但并不说明GIC送来的中断不能唤醒CPU。 因此,suspend之后,产生中断,CPU醒来,继续执行,直到全局中断再次enable(arch_suspend_enable_irqs),CPU才会跳转到指定的中断handler中去处理中断。
              • koala

                @wowo:关闭全局中断(GIC到CPU的路径),那么GIC为什么还能把中断送到CPU呢?(但并不说明GIC送来的中断不能唤醒CPU。) 那就是说关闭全局中断,只是关闭CPU相应中断的能力,但是外部的中断还是能通过GIC把CPU enable的。
              • heng

                @wowo:@wowo: 谢谢wowo的分析,我想请问wowo: 我在rk3288设置了USB唤醒功能,然后蓝牙USB接收到按键就会唤醒,这个功能我已经实现,但是其中有疑问的地方就是:到底是先蓝牙收到,然后触发USB的中断,然后唤醒,还是先触发USB中断,然后唤醒,然后才到蓝牙?
    • linuxer

      @koala:既然wowo召唤我,那我就说两句好了,虽然这里是wowo同学的主场,呵呵 其实GIC和各个CPU的接口包括两种硬件信号: (a)触发CPU中断的信号。nIRQCPU和nFIQCPU信号线,熟悉ARM CPU的工程师对这两个信号线应该不陌生,主要用来触发ARM cpu进入IRQ mode和FIQ mode。 (b)Wake up信号。nFIQOUT和nIRQOUT信号线,去ARM CPU的电源管理模块,用来唤醒CPU的 因此disable了CPU的中断仅仅是堵死了中断这一个分支而已,wakeup信号仍在正常工作中。
      • koala

        @linuxer:嗯,这下子明白了,谢谢wowo和linuxer两位大神!
  • heziq

    @wowo @linuxer struct device_driver { const char *name; struct bus_type *bus; struct module *owner; const char *mod_name; /* used for built-in modules */ bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ const struct of_device_id *of_match_table; const struct acpi_device_id *acpi_match_table; int (*probe) (struct device *dev); int (*remove) (struct device *dev); void (*shutdown) (struct device *dev); int (*suspend) (struct device *dev, pm_message_t state); int (*resume) (struct device *dev); const struct attribute_group **groups; const struct dev_pm_ops *pm; struct driver_private *p; }; 在这里面有 int (*suspend) (struct device *dev, pm_message_t state); int (*resume) (struct device *dev); 但是我在内核中没有找到使用他的地方,一般情况下使用的是struct dev_pm_ops 中的suspend和resume。既然没有使用,为什么还留着他呢? 在休眠过程中的调用 if (!callback && dev->driver && dev->driver->pm) { info = "driver "; callback = pm_op(dev->driver->pm, state); } 很明显调用的是dev->driver->pm,而没有调用dev->driver->suspend.dev是struct dev结构体 在这个Ac97_bus.c 文件中好像用了一次dev->driver->suspend
    • wowo

      @heziq:driver->suspend是旧接口,现在很少使用了。保留的目的是向前兼容。
  • linuxer

    有一个小瑕疵,文档中描述“f)调用suspend_test,判断是否为suspend to freeze,如果是,则已经完成,直接跳至h步骤。”,看起来这里表述可能有问题,对于suspend过程,如果系统要进入的电源管理状态是PM_SUSPEND_FREEZE的话,还是需要执行suspend_devices_and_enter的(步骤g)。 对于PM_SUSPEND_FREEZE,其动作包括三个部分: 1、冻结进程 2、suspended devices(电源管理状态的参数是PM_SUSPEND_FREEZE) 3、cpu进入idle
    • wowo

      @linuxer:大神怎么有闲情雅致看电源管理了?哈哈。是的,被suspend_test误导了,还不够细致啊,等下改改。
      • linuxer

        @wowo:项目中有一个issue和freezer相关,因此看的比较仔细,哈哈 另外,我马上要写中断子系统中和电源管理子系统相关的内容,因此也在阅读电源管理的代码
  • firo 主页

    非常赞! 思路清晰, 语言通俗, 内容全面!!! 脑浆炸裂一般爽啊! 从此楼主博客是我家!!!
    • wowo

      @firo:多谢夸奖~
  • firo 主页

    代码分布 2)Device PM 加上 include/linux/pm.h 如何? 另外, 这句 a)调用suspend_ops的enter回调(有的话),通知平台代码,以便让其作相应的准备(需要的话) 和代码语义不符. if (need_suspend_ops(state) && (!suspend_ops || !suspend_ops->enter)) return -EPERM; 我理解这里只是做一个检测, 没有调用.
    • wowo

      @firo:多谢指正,确实如此,等等修改。谢谢
  • firo 主页

    Hi, wowo 我现在遇到的问题, 从现象看是 在test suspend的时候, cpu被disable后不能enable. 操作下: echo processors > /sys/power/pm_test echo mem > /sys/power/mem log: Disabling non-boot CPUs ... CPU1: shutdown CPU2: shutdown CPU3: shutdown suspend debug: Waiting for 5 seconds. Enabling non-boot CPUs ... SMP: CPU 0 Waking up CPU 1 CPU1: failed to come online Error taking CPU1 up: -5 SMP: CPU 0 Waking up CPU 2 CPU2: failed to come online Error taking CPU2 up: -5 SMP: CPU 0 Waking up CPU 3 CPU3: failed to come online Error taking CPU3 up: -5 PM: noirq resume of devices complete after 80.429 msecs PM: early resume of devices complete after 0.409 msecs 我追踪看到一个奇诡的现象, 不能和理解, 我的基本信息3.14内核 arm32: cpu0 唤醒了cpu1 cpu1开始执行secondary_start_kernel开始启动自己, 调用路径: secondary_start_kernel->cpu_startup_entry->cpu_idle_loop->while(1) 在这个函数里面有个语句set_cpu_online(cpu, true);按理说, 1号cpu就是online的状态了. 而在cpu_idle_loop的while(1)的循环里面这个 if (cpu_is_offline(smp_processor_id())) arch_cpu_idle_dead(); 这个if语句里的cpu_is_offline(smp_processor_id())尽然是true. 也就是说1号cpu是offline的! wowo你认为这个现象正常吗?我没找到相关修改cpu 状态的函数逻辑.我的问题可能和这个有关.
    • firo 主页

      @firo:还有Error taking CPU1 up: -5 这个错误输出是因为相应的secondary cpu的状态是offline打出来的, 这个错误输出是cpu0打出来的, 具体函数在enable_nonboot_cpus.
    • wowo

      @firo:firo,您确认这句话成立吗:“cpu1开始执行secondary_start_kernel开始启动自己”? CPU3: failed to come online ,是__cpu_up打印的。 Error taking CPU3 up: -5 ,是enable_nonboot_cpus打印的。 没有证据显示secondary_start_kernel执行了。 我怀疑是suspend的过程中,secondary CPUs的执行环境被破坏了,没有机会执行。
      • firo 主页

        @wowo:你的推理很可能是正确的! 我之前在cpu_die这个函数里面加了句dump_stack, 打出的bt栈底是secondary_start_kernel, 我以为secondary CPUs正常启动了. 实际是disable_nonboot_cpu处罚了secondary CPUs在idle_cpu_loop里面的cpu_die的执行, 从而输出了bt. 看这样子还是secondary CPUs没有正确跳转到初始化的代码上! 我今天, 看代码发现arch/arm/mach-mybsp/platsmp.c的代码里面竟然没有正确的初始化smp_ops.cpu_kill这个成员. 我是从内核3.2直接把这个suspend和idle功能迁移到内核3.14上的. 内核3.2的代码里面没有smp_operations这个结构(我没找到).cpu die, cpu kill这些功能在3.2的内核都是通过在mach-*/下面定义platform_cpu_die/kill这样的全局函数, 从而在被smp子系统调用的. 另外, 我看到http://events.linuxfoundation.org/sites/events/files/slides/clement-smp-bring-up-on-arm-soc.pdf 这个doc里面说cpu kill 和 cpu die之间要同步. 我怀疑可能和这个有关, 不过感觉概率不大. 至于你说的"secondary CPUs的执行环境被破坏了" 这种可能性很大. 我追踪过CPU0 调用smp_boot_secondary平台相关的代码都被执行了. 这些代码在3.2是好用的, 另外代码的逻辑非常简单: 1. smp cross call 发IPI wakeup唤醒 相应的secondary CPUs 2. 设置secondary CPUs 的boot address. 3. 清除 secondary CPUs的reset 标志位. 不知道哪里能破坏secondary CPUs的执行环境.wowo有没有可能的怀疑点?
        • firo 主页

          @firo:我很可能是因为disable nonboot cpus这个路径上包含了一些从3.2移过来的代码导致的. 就多了这两句, #ifdef CONFIG_SHEEVA_DEEP_IDLE armadaxp_fabric_prepare_deepIdle(); armadaxp_fabric_prepare_hotplug(); #endif 这个CONFIG_SHEEVA_DEEP_IDLE配置项被打开了.
        • wowo

          @firo:可以从两个方面入手:secondary是否真的被唤醒?boot Address是否正确?Address里面的内容是否正确? 因为boot CPU在醒着,这些东西还是很好查的。
          • firo 主页

            @wowo:Thanks! 我试着排查一下.
          • firo 主页

            @wowo:Hi, wowo, 阶段性的进展: 调试发现在smp_boot_secondary这个函数的步骤3 清除 secondary CPUs的reset 标志位. 之前Secondary CPUs的reset标志位就是没置位的! 我怀疑问题出在disable_nonboot_cpus 和 cpu_die里面. 追到了wfi, 也没看到相关的设置reset标志位的调用. 另外, 查看硬件手册, 有上这样的描述: Place the CPU to Wait For Interrupt mode. When the CPU enters this state, the following steps occur: a) CPU activity is halted, outstanding instructions are completed, and the write buffer is drained out. b) The CPU clocks are halted. c) An internal reset is generated to the CPU. d) The PMU power down the CPU core. 貌似是硬件, 来设置这个reset标志位.
            • wowo

              @firo:这部分已经是arch-dependent的了,取决于您所使用的平台的特性。cpu die和cpu boot的实现,要相互配合。另外,可以检查一下,cpu die中是否有主动关clock、关power?
              • firo 主页

                @wowo:die的实现: 1. 设置唤醒事件, 和在可能的情况下power down l2 2. disable cpu snoop, 这个貌似和cpu的coherency有关. 3. 进入wfi
              • firo 主页

                @wowo:原因找到了, 因为kernel menuconfig 的原因导致一段汇编代码没编译. 哎....
  • bt_baseband 主页

    I found a type error: "CPU hotplug、wakeup处理等----过----个知识点"
    • wowo

      @bt_baseband:多谢提醒,稍后改正:-)
  • js_wawayu

    请教一个问题, cmdline中boot_cpus=0,1,2,3,4,5, 但是在系统suspend的时候如下的log: Disabling non-boot CPUs ... ]psci: Retrying again to check for CPU kill ]psci: CPU1 killed. psci: CPU2 killed. psci: CPU3 killed. psci: CPU4 killed. psci: CPU7 killed. 按照cmdline cpu1,2,3,4都属于boot cpu为什么在suspend时候disable non-boot cpu的时候把这几个cpu也关了?boot cpu是不是只能有一个cpu0? 谢谢!
    • wowo

      @js_wawayu:我找了很久,没有发现boot_cpus的cmdline,你用的是什么系统呢?
      • js_wawayu

        @wowo:哦,我理解错了,应该是dts里定义的,反正boot_cpus定义了这五个cpu。
        • wowo

          @js_wawayu:我觉得这里的“boot_cpus”,并不是我们所理解的那个boot cpu。具体要看你的代码逻辑了,我这边看不到:-)
          • js_wawayu

            @wowo:smp.c里有下面的代码: early_param("boot_cpus", boot_cpus); kernel-parameter.txt里有如下的解释: early_param("boot_cpus", boot_cpus); 我用的是高通平台
            • js_wawayu

              @js_wawayu:写错了kernel-parameter.txt里的解释如下: boot_cpus= [SMP] Rather than attempting to online all possible CPUs at boot time, only online the specified set of CPUs.
              • wowo

                @js_wawayu:看解释,就很清楚了,不是boot/secondary的意思,
  • bat0301

    窝窝好,请教一个问题: charger中断线程里面是通过i2c读寄存器,但是现在发现系统在charger中断线程执行的时候,系统正在进入休眠,中断被关了,导致读i2c失败(i2c通过中断收发)。 为什么中断线程刚进去,系统居然睡眠了呢?另外我试着在中断线程里面使用wake_lock进行唤醒系统,最终却没有阻值系统的睡眠。我查了下,events_check_enabled居然一直是flase。 我想知道两个问题:1.为何中断线程执行的时候系统会睡眠?2.为何wake_lock不起作用。
    • wowo

      @bat0301:events_check_enabled为false是对的,具体可参考/pm_subsystem/wakeup_count.html。 中断线程是在进程空间执行,可以睡眠。 wake_lock不起作用?你是怎么用的呢?
      • bat0301

        @wowo:谢谢窝窝的耐心解答。我在线程里面执行wake_lock函数。我的理解是执行完后,系统不应该继续睡眠,实际却像没发生一样继续睡眠了。是不是wake_lock的使用有问题? 关于events_check_enabled为flase我再研究研究代码。
        • bat0301

          @bat0301:在suspend_enter接口中,suspend前的最后一刻,会调用pm_wakeup_pending接口,而pm_wakeup_pending函数里面,如果events_check_enabled为flase,则直接返回0。如果有唤醒事件发生(返回非0),如果有就要终止suspend,也就是执行syscore_resume。 按道理,我在我的线程里面使用wake_lock,就是产生了一个唤醒的事件,但是打印看到events_check_enabled全程都是0,那就不难解释为什么睡眠没有被打断了,因为睡眠过程中,pm_wakeup_pending一直返回0,所以根本检测不到来了唤醒事件吧? 不知道我是wake_lock用法不对导致的还是其他原因?
          • wowo

            @bat0301:返回0,不会进入这个if语句: *wakeup = pm_wakeup_pending(); if (!(suspend_test(TEST_CORE) || *wakeup)) { error = suspend_ops->enter(state); events_check_enabled = false; } 系统才会终止suspend。
  • junjun

    linux kernel documen文档devices.txt里有说"The suspend_noirq phase occurs after IRQ handlers have been disabled, ", 没有明白这里的IRQ handler怎么对应到我的platform_device上,我现在platform_driver的suspned_noirq会被调用到,但并没有相关的IRQ啊,当然也没有IRQ handler disable。求博主解惑啊,暂时还没有能力研究PM内部的代码。
    • wowo

      @junjun:它的意思是,当调到各个driver的suspend_noirq时,系统的irq已经被disable了,你可以放心的做你想做的事情。 当然,什么事情需要在中断被disable的时候做,只有这个driver自己知道了。
      • lamaboy

        @wowo:你好,, wowo ,看 你上面的讨论,, 我正在看最新的源码 4.5 ,发现是 suspend_noirq ,调用之后才禁止irq,,这样,, 不是就把逻辑改变了吗,还是我没有注意到呀,, 代码如下; static int suspend_enter(suspend_state_t state, bool *wakeup) { int error; error = platform_suspend_prepare(state); if (error) goto Platform_finish; error = dpm_suspend_late(PMSG_SUSPEND); if (error) { printk(KERN_ERR "PM: late suspend of devices failed\n"); goto Platform_finish; } error = platform_suspend_prepare_late(state); if (error) goto Devices_early_resume; error = dpm_suspend_noirq(PMSG_SUSPEND); if (error) { printk(KERN_ERR "PM: noirq suspend of devices failed\n"); goto Platform_early_resume; } error = platform_suspend_prepare_noirq(state); if (error) goto Platform_wake; if (suspend_test(TEST_PLATFORM)) goto Platform_wake; /* * PM_SUSPEND_FREEZE equals * frozen processes + suspended devices + idle processors. * Thus we should invoke freeze_enter() soon after * all the devices are suspended. */ if (state == PM_SUSPEND_FREEZE) { trace_suspend_resume(TPS("machine_suspend"), state, true); freeze_enter(); trace_suspend_resume(TPS("machine_suspend"), state, false); goto Platform_wake; } error = disable_nonboot_cpus(); 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)) { trace_suspend_resume(TPS("machine_suspend"), state, true); error = suspend_ops->enter(state); trace_suspend_resume(TPS("machine_suspend"), state, false); events_check_enabled = false; } else if (*wakeup) { error = -EBUSY; } syscore_resume(); } arch_suspend_enable_irqs(); BUG_ON(irqs_disabled()); Enable_cpus: enable_nonboot_cpus(); Platform_wake: platform_resume_noirq(state); dpm_resume_noirq(PMSG_RESUME); Platform_early_resume: platform_resume_early(state); Devices_early_resume: dpm_resume_early(PMSG_RESUME); Platform_finish: platform_resume_finish(state); return error; }
        • wowo

          @lamaboy:抱歉,你是对的(顺便想楼上的那么老兄道歉),我表达的太随意。 这里的suspend_noirq,其实是针对当前被suspend的这个设备(而不是系统),因此这里的noirq,是指设备已经没有产生irq的能力了(因为已经先调过suspend了)。
  • heng

    谢谢wowo的分析,我想请问wowo: 我在rk3288设置了USB唤醒功能,然后蓝牙USB接收到按键就会唤醒,这个功能我已经实现,但是其中有疑问的地方就是:到底是先蓝牙收到,然后触发USB的中断,然后唤醒,还是先触发USB中断,然后唤醒,然后才到蓝牙?
    • wowo

      @heng:你的场景应该是这样的吧: 蓝牙键盘_____/ ~~ \____USB蓝牙适配器<-------USB------>RK3288 抛开键盘不说,你的环境里面其实有两个大脑:适配器和RK3288. 因此你所说的唤醒,其实是RK3288的唤醒,也就是说,RK3288在睡觉的时候,蓝牙适配器其实是醒着的。 所以你觉得应该是怎么样的顺序呢?
      • heng

        @wowo:哦~这下清楚了~就是先到蓝牙的。原来蓝牙适配器是醒着的,我一直以为蓝牙也休眠了呢。谢谢wowo。那这个是不是就像android的联网,手机虽然休眠了,但是联网功能还正常在,然后消息来了就会唤醒?
        • wowo

          @heng:是的。当然,蓝牙模块也有一个完整的系统,它内部也有可能有睡觉的东西,但这是微观层面的事情了,从3288的角度看,可以忽略了。
          • heng

            @wowo:明白了!谢谢wowo
  • fear

    博主你好, suspend后会disable irq,那请问按下power键是如何唤醒手机的呢。(中断已经disable了)。
    • wowo

      @fear:这篇文章有个评论是关于这个问题的,你可以往下翻翻看一下(差不多是评论的第二页)。
  • time

    @wowo请教一个问题. 目前做的项目遇到系统在睡眠的时候kernel panic, 随后在cmdline中添加"no_console_suspend"参数,就可以定位问题. 我想问的是: 是不是一旦加上no_console_suspend系统就无法睡眠?
    • wowo

      @time:可以睡。只是console不会优雅的睡去,会坚持打印知道最后一刻。
      • Rafe

        @wowo:@wowo 大神,请教个问题,我发现resume的时候,resume console花了比较多的时间,接上@time的问题,如果系统默认就是no_console_suspend, 除了您提及的不会优雅的睡去,是否还会有什么其他影响?(PS.目前在做项目需要缩短resume时间......实际测试后发现,no_console_suspend确实比console suspend少花些时间,但不确定能否更改默认配置)
        • wowo

          @Rafe:建议你正向看一下(例如用printk打印时间戳),为什么resume console花了比较多的时间。 这个事情很多年前我优化启动时间的时候专门处理过,不过现在记不清了,好像是和VT有关,你可以查查看。
  • linux_tt

    @wowo: 很漂亮的文章。 另请教个问题, 对于smp多核系统, 如果其他cpu当前进入cpuidle状态,当前cpu执行disable_noboot_cpus, 是否需要把其他cpu从cpuidle状态中唤醒过来,然后再执行migrate task和halt其他cpu的动作等,这里面的流程是怎样的?谢谢。
    • wowo

      @linux_tt:往简单的说,是要的。 往复杂的说,要看是什么样的arch和platform,相关的设计是怎样的(例如怎么样才算是disabled?在硬件上的行为是什么?)。 我们以简单的为例(arm64),大概的过程就是: 1. cpu只能自己死掉。 2. 当其它cpu调用disable_noboot_cpus disable某一个cpu时候,实际的过程是: 设置offline标记; 执行migrate task; 使该cpu idle; 该cpu的idle thread中在进入idle之前,检查offline标记,如果有,自杀,如果没有,进idle(从这里可以看到,自杀也是有工作量的,不能在idle的时候自杀啊)。
    • linux_tt

      @linux_tt:@wowo. 谢谢详细解答,目前在单核arm ca9上,没有机会去跟踪多核suspend流程,所以一直有这个疑问。 比如两个ca9,core1某个时刻进入cpuidle(假如只进入某种浅睡眠,clk gating态,core不掉电)。这个时候core0执行suspend,我的理解这个时core0应该把core1 wakeup起来,然后core1再去shutdown. 另外,这里core1如果只是clk gating而不不掉电,整个chip是无法完全休眠的。这里唤醒理解应该是通过IPI中断,但是看了半天没有发现这个代码流程,如果遇到过类似的项目经验,还请多多指点。
      • wowo

        @linux_tt:这里的流程和具体的硬件关系比较大,建议你还是对照着一个硬件平台去看,例如suspend状态下,core到底关闭了哪些东西、保留了哪些东西,平台的spec上会写的清清楚楚的。
  • 扑街的鸡

    @wowo 你好,我是个初学者,我想请教一个问题。 我在用户空间执行system("echo mem >/sys/power/state");后进入休眠 这是我拉高了我的DTR,系统就被唤醒了。 在休眠的流程中不是有一步是disable irq么,为什么DTR中断还可以唤醒系统呢?
    • wowo

      @扑街的鸡:DTR是什么东西呢?你可以确认一下,休眠后,DTR有关的中断,是否真的被disable了。
  • ftretwt

    您好,请教一个问题,我想增加点触摸屏唤醒屏幕,要在那个文件中的那块修改代码呢?谢谢
    • wowo

      @ftretwt:这个问题有点宽泛的。 要怎么唤醒屏幕呢?正常的流程是,触摸屏有关的硬件可以产生可唤醒系统的中断,系统醒了之后,应用程序(例如android的各个manager)再决定下一步要怎么办。 所以关于这个问题,你还是需要多自己思考之后再问比较好。
  • jay

    Hi wowo, 在linux的suspend过程中,我看到处理的流程是先调用sys_sync()来同步文件系统,然后再在suspend_prepare()中调用suspend_freeze_processes()来冻结进程。这会导致在同步文件系统之后可能会有进程访问文件系统,这样会不会出现问题?
    • wowo

      @jay:可以去fs/sync.c中看看sys_sync的实现,是需要在进程/线程的上下文中执行的,因此不能放在freeze之后。 关于你的疑问,suspend过程走到这里的时候,用户空间的机制要保证不会出问题,例如要么不用于进程访问文件系统,要么进程访问文件系统之后终止suspend过程,要么进程访问文件系统之后自己sync。 再发散一下,在较新版本的kernel中,增加CONFIG_SUSPEND_SKIP_SYNC配置项,如果使能,则不会执行sync动作(也就是说完全交由用户空间程序处理了): config SUSPEND_SKIP_SYNC bool "Skip kernel's sys_sync() on suspend to RAM/standby" depends on SUSPEND depends on EXPERT help Skip the kernel sys_sync() before freezing user processes. Some systems prefer not to pay this cost on every invocation of suspend, or they are content with invoking sync() from user-space before invoking suspend. Say Y if that's your case.
      • fdm_dec

        @wowo:为什么要进行文件系统同步呢?如果在cache中(ram),str不是不会丢失ram的数据吗?
        • snyh

          @fdm_dec:如果s2ram或s2idle持续时间非常久,导致断电,不在freeze前做sync,就会导致磁盘数据损坏等问题
  • 君莫笑

    linux小菜一枚,一直会接触suspend,来来回回看了好久这个系列的博客,终于今天看懂了,问一个问题,前辈! 我现在用的hisi3516的片子,但是平台没有实现suspend_ops->enter(state),我要怎么做才能让他待机呢,能实现吗?
  • TGSP 主页

    您最后面的图的调用顺序好像有个小错误,在resume流程中,P.finish应该是介于D.resume_late和D.resume之间,顺带着带着P.suspend_again下移;而且左边suspend流程的一开始,并没有执行P.begin,而是判断该函数是否实现。您看,我分析的是否正确?
    • wowo

      @TGSP:是吗?时间太长了,等有空了我在拔一下代码看看,是否如你所说的那样。
  • crash

    一直觉得很费解,假如suspend的过程一切顺利,进入到suspend_ops->enter,休眠一段时间后,外部中断唤醒产生wakeup event,这时候程序是从哪里开始跑起来的呢,继续events_check_enabled = false;这样往下跑吗 { *wakeup = pm_wakeup_pending(); if (!(suspend_test(TEST_CORE) || *wakeup)) { error = suspend_ops->enter(state); events_check_enabled = false; } syscore_resume(); }
    • wowo

      @crash:正常情况下都是这样的。 除非有些芯片在这种suspend状态下CPU掉电了。
  • dimmomo

    hello,问下哈 我在用imx6做linux开发, 系统可以睡眠,但唤醒时有时会系统reset。您知道是为什么么
  • fdm_dec

    有个疑问,如果suspend后cpu掉电了(ram数据还在),那唤醒后应该通常不是从resume后继续执行吧,我的理解应该是reset吧,那是否在suspend时保存cpu的寄存器上下文,然后在boot的第一句代码恢复上下文到进入休眠那句代码的下一行了?你这里指的唤醒后继续执行resmue代码,是否指的是cpu不掉电的行为呢?那通常嵌入式端是休眠时cpu是要掉电的吗?
    • wowo

      @fdm_dec:如果suspend to ram的话,CPU一般不会掉电。 嵌入式端的休眠,具体怎么做的话,要看这个CPU的实现。例如有些MCU,有SLEEP、STOP等各种模式,其中应该会有一种MCU会掉电的模式。 如果CPU掉电的话,可以保存CPU寄存器的上下文,linux里面suspend to disk(hibernate)就是这个原理。
  • xy

    你好,我想问一下suspend_ops->enter( );这个回调函数具体做了哪些事情呢? 或者从哪里可以看到这个回调函数的定义呢? 为什么进入该函数之后系统就是suspend状态了,恢复的时候又是如何从这个函数中返回的?
  • nick

    Hi, @wowo 你好,文章很不错哦,我是个初学者,学到了很多。现在我正在学suspend框架,有个地方不懂,我想请教一个问题: 系统在调用suspend_ops->enter()在前既然已经关闭了全局中断,那系统是怎样被触发唤醒的呢?
    • wmc

      @nick:The IRQF_NO_SUSPEND flag is used to indicate that to the IRQ subsystem when requesting a special-purpose interrupt. It causes suspend_device_irqs() to lea ve the corresponding IRQ enabled so as to allow the interrupt to work as expected during the suspend-resume cycle, but does not guarantee that the interrupt will wake the system from a suspended state -- for such cases it is necessary to use enable_irq_wake(). ------------------------ 可以使用这个接口标记一个中断可唤醒system
  • 默默成长

    有个问题请教下。睡眠流程中有workqueue相关的操作,文章当中没有提到这点,能够普及一下吗?
    • caid

      @默默成长:答案应该在该系列下“Linux进程冻结技术” 这篇后续文章中。也就是suspend_prepare中的具体内容 “system_freezing_cnt>0表示系统进入冻结,pm_freezing=true表示冻结用户进程,pm_nosig_freezing=true表示冻结内核线程和workqueue。它们会在freeze_processes和freeze_kernel_threads中置位,在thaw_processes和thaw_kernel_threads中清零。” autosleep中有调用pm_get_wakeup_count获取wakeup_count,如果获取到wake_lock则去排队,等待下次机会suspend. queue_up_suspend_work 我以为Linux电源管理 7/8/9三篇文章顺序倒过来(9、8、7)读比较合适
  • caid

    suspend_enter 的f5步骤看了好一会,才意识到4.14版本中已经更新了 其中PM_SUSPEND_FREEZE 改为了 PM_SUSPEND_MEM f5步骤去掉了,是直接检查 是否为IDLE,若是则resume,若不是 则继续流程 代码以 platform_suspend 与 dpm_suspend开头的函数来区分
  • nemo

    wakeup处理等过个知识点 = > 多个知识点
  • mubai

    wowo您好,我们这边有个奇怪的现象,是概率性的,通过在bl31的代码里增加打印的方式,发下系统suspend的流程走到.cpu_kill的时候执行__invoke_psci_fn_smc后没有陷入到ATF中,想请教下会有哪方面的原因?