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

作者:wowo 发布于:2014-8-22 21:40 分类:电源管理子系统

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定义了一个名称为stateattribute文件,该文件的store接口为state_store,该接口在lockautosleep功能后,解析用户传入的bufferfreezestandby 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_switchdrivers\tty\vt\vt_ioctl.c),方便其它内核模块从整体上关闭或者开启VT switch功能。

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

        a)有console driver调用pm_vt_switch_required接口,显式的要求使能VT switchPM coreconsole模块会把这些信息记录在一个名称为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实现,主要是holdconsole用的互斥锁,使他人无法使用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功能,是suspendhibernate等电源管理功能的组成部分,在新版本内核中,它被独立出来,作为一个独立的电源管理状态(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_preparesuspend_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进程等待driverresume

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

drivers\video\omap2\dss\core.c  

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

Linux驱动工程师来说,device PM opsplatform 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简称Dstruct 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

评论:

jay
2017-12-13 10:34
Hi wowo,

在linux的suspend过程中,我看到处理的流程是先调用sys_sync()来同步文件系统,然后再在suspend_prepare()中调用suspend_freeze_processes()来冻结进程。这会导致在同步文件系统之后可能会有进程访问文件系统,这样会不会出现问题?
wowo
2017-12-13 13:43
@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.
ftretwt
2017-11-04 18:07
您好,请教一个问题,我想增加点触摸屏唤醒屏幕,要在那个文件中的那块修改代码呢?谢谢
wowo
2017-11-06 09:13
@ftretwt:这个问题有点宽泛的。
要怎么唤醒屏幕呢?正常的流程是,触摸屏有关的硬件可以产生可唤醒系统的中断,系统醒了之后,应用程序(例如android的各个manager)再决定下一步要怎么办。
所以关于这个问题,你还是需要多自己思考之后再问比较好。
扑街的鸡
2017-09-07 10:07
@wowo 你好,我是个初学者,我想请教一个问题。
我在用户空间执行system("echo mem >/sys/power/state");后进入休眠
这是我拉高了我的DTR,系统就被唤醒了。 在休眠的流程中不是有一步是disable irq么,为什么DTR中断还可以唤醒系统呢?
wowo
2017-09-07 13:25
@扑街的鸡:DTR是什么东西呢?你可以确认一下,休眠后,DTR有关的中断,是否真的被disable了。
linux_tt
2017-07-21 11:35
@wowo: 很漂亮的文章。

另请教个问题,
对于smp多核系统,
如果其他cpu当前进入cpuidle状态,当前cpu执行disable_noboot_cpus,
是否需要把其他cpu从cpuidle状态中唤醒过来,然后再执行migrate task和halt其他cpu的动作等,这里面的流程是怎样的?谢谢。
wowo
2017-07-21 17:03
@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
2017-07-22 15:39
@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
2017-07-24 09:07
@linux_tt:这里的流程和具体的硬件关系比较大,建议你还是对照着一个硬件平台去看,例如suspend状态下,core到底关闭了哪些东西、保留了哪些东西,平台的spec上会写的清清楚楚的。
time
2017-06-19 20:39
@wowo请教一个问题.

目前做的项目遇到系统在睡眠的时候kernel panic, 随后在cmdline中添加"no_console_suspend"参数,就可以定位问题.

我想问的是: 是不是一旦加上no_console_suspend系统就无法睡眠?
wowo
2017-06-20 10:45
@time:可以睡。只是console不会优雅的睡去,会坚持打印知道最后一刻。
fear
2017-04-14 10:09
博主你好,
   suspend后会disable irq,那请问按下power键是如何唤醒手机的呢。(中断已经disable了)。
wowo
2017-04-17 08:59
@fear:这篇文章有个评论是关于这个问题的,你可以往下翻翻看一下(差不多是评论的第二页)。
heng
2016-08-18 15:16
谢谢wowo的分析,我想请问wowo:
我在rk3288设置了USB唤醒功能,然后蓝牙USB接收到按键就会唤醒,这个功能我已经实现,但是其中有疑问的地方就是:到底是先蓝牙收到,然后触发USB的中断,然后唤醒,还是先触发USB中断,然后唤醒,然后才到蓝牙?
wowo
2016-08-18 18:22
@heng:你的场景应该是这样的吧:
蓝牙键盘_____/  ~~  \____USB蓝牙适配器<-------USB------>RK3288
抛开键盘不说,你的环境里面其实有两个大脑:适配器和RK3288.
因此你所说的唤醒,其实是RK3288的唤醒,也就是说,RK3288在睡觉的时候,蓝牙适配器其实是醒着的。

所以你觉得应该是怎么样的顺序呢?
heng
2016-08-19 08:32
@wowo:哦~这下清楚了~就是先到蓝牙的。原来蓝牙适配器是醒着的,我一直以为蓝牙也休眠了呢。谢谢wowo。那这个是不是就像android的联网,手机虽然休眠了,但是联网功能还正常在,然后消息来了就会唤醒?
wowo
2016-08-19 09:23
@heng:是的。当然,蓝牙模块也有一个完整的系统,它内部也有可能有睡觉的东西,但这是微观层面的事情了,从3288的角度看,可以忽略了。
heng
2016-08-19 11:01
@wowo:明白了!谢谢wowo
junjun
2016-08-11 18:38
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
2016-08-11 19:18
@junjun:它的意思是,当调到各个driver的suspend_noirq时,系统的irq已经被disable了,你可以放心的做你想做的事情。
当然,什么事情需要在中断被disable的时候做,只有这个driver自己知道了。
lamaboy
2016-08-15 22:01
@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
2016-08-15 22:19
@lamaboy:抱歉,你是对的(顺便想楼上的那么老兄道歉),我表达的太随意。
这里的suspend_noirq,其实是针对当前被suspend的这个设备(而不是系统),因此这里的noirq,是指设备已经没有产生irq的能力了(因为已经先调过suspend了)。
bat0301
2016-07-25 20:28
窝窝好,请教一个问题:
  charger中断线程里面是通过i2c读寄存器,但是现在发现系统在charger中断线程执行的时候,系统正在进入休眠,中断被关了,导致读i2c失败(i2c通过中断收发)。
  为什么中断线程刚进去,系统居然睡眠了呢?另外我试着在中断线程里面使用wake_lock进行唤醒系统,最终却没有阻值系统的睡眠。我查了下,events_check_enabled居然一直是flase。
  我想知道两个问题:1.为何中断线程执行的时候系统会睡眠?2.为何wake_lock不起作用。
wowo
2016-07-26 08:59
@bat0301:events_check_enabled为false是对的,具体可参考http://www.wowotech.net/pm_subsystem/wakeup_count.html。
中断线程是在进程空间执行,可以睡眠。
wake_lock不起作用?你是怎么用的呢?
bat0301
2016-07-26 14:25
@wowo:谢谢窝窝的耐心解答。我在线程里面执行wake_lock函数。我的理解是执行完后,系统不应该继续睡眠,实际却像没发生一样继续睡眠了。是不是wake_lock的使用有问题?

关于events_check_enabled为flase我再研究研究代码。
bat0301
2016-07-26 14:48
@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
2016-07-26 18:49
@bat0301:返回0,不会进入这个if语句:
                *wakeup = pm_wakeup_pending();                                  
                if (!(suspend_test(TEST_CORE) || *wakeup)) {                    
                        error = suspend_ops->enter(state);                      
                        events_check_enabled = false;                          
                }
系统才会终止suspend。
js_wawayu
2016-07-15 16:58
请教一个问题,
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
2016-07-16 09:06
@js_wawayu:我找了很久,没有发现boot_cpus的cmdline,你用的是什么系统呢?
js_wawayu
2016-07-16 22:55
@wowo:哦,我理解错了,应该是dts里定义的,反正boot_cpus定义了这五个cpu。
wowo
2016-07-17 17:22
@js_wawayu:我觉得这里的“boot_cpus”,并不是我们所理解的那个boot cpu。具体要看你的代码逻辑了,我这边看不到:-)
js_wawayu
2016-07-18 11:06
@wowo:smp.c里有下面的代码:
early_param("boot_cpus", boot_cpus);
kernel-parameter.txt里有如下的解释:
early_param("boot_cpus", boot_cpus);

我用的是高通平台
js_wawayu
2016-07-18 11:07
@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
2016-07-18 13:09
@js_wawayu:看解释,就很清楚了,不是boot/secondary的意思,

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog