0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

电源管理休眠唤醒的基本概念和框架解析

yzcdx 来源: OS与AUTOSAR研究 2023-11-16 16:30 次阅读

当我们不用设备的时候,一般需要关机,用的时候再开机,这样有一个问题,开机非常的慢,那么有什么方法即省电又可以快速开机呢?

答案就是休眠唤醒suspend、resume。甚至很多消费者设备例如手机汽车都是假关机,其实还是休眠,这样用户体验好啊,随时用几秒就可以唤醒使用,用户体验才是王道。

休眠唤醒很重要,一般指的是STR(stroe to RAM),其技术涉及范围很广,需求也很多。有电池的设备可以省电,另外就是电源供电的也可以延长设备使用寿命。

1.基本概念和框架

1.1 基本概念

STR

一般的嵌入式产品仅仅只实现了挂起到RAM(也简称为s2ram,或常简称为STR),即将系统的状态保存于内存中,并将SDRAM置于自刷新状态,待用户按键等操作后再重新恢复系统。

STD

少数嵌入式Linux系统会实现挂起到硬盘(简称STD),它与挂起到RAM的不同是s2ram并不关机,STD则把系统的状态保持于磁盘,然后关闭整个系统。

这些休眠方式,可以通过操作设备节点/sys/power/state设置freeze、standyby、STR(suspend to RAM)和STD(suspend to disk)去实现。通过写入”freeze”、”standby”和”mem”,即可触发它们。

休眠后,可以通过唤醒源(按键、RTC、屏幕、USB拔插等)对系统进行唤醒,唤醒源是不休眠的,需要保留下来监听唤醒操作。

1.2 休眠唤醒技术框架

8b6d82d2-8456-11ee-939d-92fbcf53809c.png

上层service通过wakelock的使用,在系统不需要工作的时候经由power manager利用PM core提供的文件节点发起休眠。

PM core实现power manage的核心逻辑,为上层services提供操作休眠唤醒的相关接口,通过利用底层相关的技术实现休眠唤醒过程中的cpu hotplug、wakup source enable/disable、设备的suspend&resume等。

休眠的过程中PM driver会配置、取消唤醒源,调用设备的suspend&resume函数,进行syscore的suspend&resume操作。

Services

Services部分由两类service组成,power manager service及普通的app service。其中,power manager service提供了wakelock锁的create/request/release管理功能,当没有services持有wakelock的话,power manager service会通过往文件节点/sys/power/state写mem发起内核的休眠。

PM core

PM core部分提供了wakelock(决定是否发起休眠)的实现,wakeup_count(用于各services释放wakelock后,到发起内核休眠的期间是否有唤醒源,从而是否进行resume的管理)的实现,suspend的实现。这三个功能分别向上层提供了相应的文件节点,供上层操作。休眠、唤醒的过程中会涉及到进程的freeze&thaw,wakeup source的使能、失能,设备的休眠、唤醒,power domain的关、开,cpu的拔、插等功能或框架。相关代码如下:

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过程中对进程的处理逻辑

PM driver

PM driver部分主要实现了设备驱动的suspend&resume实现,架构驱动(gpio、irq、timer等)低功耗相关的操作。

//Device PM
drivers/base/power/* 

//Platform dependent PM
include/linux/suspend.h----定义platform dependent PM有关的操作函数集
arch/xxx/mach-xxx/xxx.c或者
arch/xxx/plat-xxx/xxx.c----平台相关的电源管理操作

suspend&resume过程概述

8b8eb0b0-8456-11ee-939d-92fbcf53809c.png2. 核心代码分析

echo mem > /sys/power/state

做如上操作后,整个函数调用流程如下:

8ba9e89e-8456-11ee-939d-92fbcf53809c.png

其中设置suspend的核心函数为suspend_enter, 如下:

8bc98302-8456-11ee-939d-92fbcf53809c.png

相关功能代码见:kernel/power/main.c和suspend.c等文件。

Linux内核Suspend总体流程如下:

state_store()->
    pm_suspend()->
        pm_suspend_marker("entry")      ## 1、标记进入睡眠
        enter_state()->                 ## 2、处理睡眠相关工作,重点关注
            sys_sync()                      ## 2.1、同步文件系统
            suspend_prepare()               ## 2.2、准备进入系统睡眠状态,并冻结用户空间进程和内核线程
            suspend_devices_and_enter()     ## 2.3、休眠外设并进入系统睡眠状态,该函数在系统唤醒时返回
            suspend_finish()                ## 2.4、睡眠结束并被唤醒
        pm_suspend_marker("exit")       ## 3、标记退出睡眠

下面重点介绍suspend_devices_and_enter()函数的流程:

suspend_devices_and_enter()->
    ## 1、冻结串口,可以在u-boot传入no_console_suspend,释放suspend流程中串口打印
    suspend_console()
    
    ## 2、外设驱动suspend
    dpm_suspend_start()->       
        dpm_prepare()->         
            device_prepare()    ## 执行设备电源管理函数中的prepare函数
        dpm_suspend()->
            device_suspend()->
                __device_suspend()->
                    dpm_run_callback()->
                        initcall_debug_start()  ## 显示调用的各suspend()函数名等信息,需要打开pm_print_times
                        cb()                    ## 执行各.suspend()函数,包括:外设驱动,电源域,总线等(重点关注****)
                        initcall_debug_report() ## 显示各suspend()函数返回值和执行时间
    
    ## 3、系统进入睡眠状态,该流程同时处理唤醒操作
    suspend_enter()->           
        platform_suspend_prepare()
        dpm_suspend_late(PMSG_SUSPEND)->
            device_suspend_late()->             
                __device_suspend_late()->
                    dpm_run_callback()->
                        initcall_debug_start()  ## 显示调用的各suspend()函数名等信息,需要打开pm_print_times
                        cb()                    ## 执行各.suspend_late()函数  (重点关注****)
                        initcall_debug_report() ## 显示各.suspend_late()函数返回值和执行时间
        
        dpm_suspend_noirq(PMSG_SUSPEND)->
            device_suspend_noirq()->
                __device_suspend_noirq()->
                    dpm_run_callback()->
                        initcall_debug_start()  ## 显示调用的各suspend()函数名等信息,需要打开pm_print_times
                        cb()                    ## 执行各.suspend_noirq()函数  (重点关注****)
                        initcall_debug_report() ## 显示各.suspend_noirq()函数返回值和执行时间                    
                    
        disable_nonboot_cpus()          ## 冻结非启动cpu
        arch_suspend_disable_irqs()     ## 关中断
        syscore_suspend()               ## 执行注册在syscore_ops_list上的syscore_ops的suspend

 ##################################### 开始唤醒,流程和suspend相反 #######################
        
        syscore_resume()
        arch_suspend_enable_irqs()
        enable_nonboot_cpus()
        
        dpm_resume_noirq(PMSG_RESUME)
        
        dpm_resume_early()
        platform_resume_finish()
        dpm_resume_end(PMSG_RESUME)
        resume_console()

3. 详细分析

3.1 suspend sys节点入口

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

echo "freeze" > /sys/power/state
echo "standby" > /sys/power/state
echo "mem" > /sys/power/state

系统初始化时候调用pm_init函数,在kernel/power/main.c中

power_kobj = kobject_create_and_add("power", NULL);
if (!power_kobj)
        return -ENOMEM;
error = sysfs_create_group(power_kobj, &attr_group);
if (error)
        return error;

根据sys节点的属性命令规则,sysfs接口实现此节点的实现代码为: state_store

static struct attribute_group attr_group = {
        .attrs = g,
};

static struct attribute * g[] = {
        &state_attr.attr,
        。。。
}
power_attr(state);
#define power_attr(_name) 
static struct kobj_attribute _name##_attr = {        
        .attr        = {                                
                .name = __stringify(_name),        
                .mode = 0644,                        
        },                                        
        .show        = _name##_show,                        
        .store        = _name##_store,                
}

3.2 state_store&pm_suspend

这样操作state的时候就会执行state_store()函数

static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
                           const char *buf, size_t n)
{
        suspend_state_t state;
        int error;
//给autosleep功能加锁
        error = pm_autosleep_lock();
        if (error)
                return error;
//autosleep_state值大于0,已经工作在suspend了则停止
        if (pm_autosleep_state() > PM_SUSPEND_ON) {
                error = -EBUSY;
                goto out;
        }
//解析用户命令中的buf,例如mem
        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,在includelinuxsuspend.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)

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

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;
}
EXPORT_SYMBOL(pm_suspend);

3.3 enter_state

enter_state()函数中:

static int enter_state(suspend_state_t state)
{
        int error;
//确保没有人试图将系统置于睡眠状态
        trace_suspend_resume(TPS("suspend_enter"), state, true);
        if (state == PM_SUSPEND_FREEZE) {
#ifdef CONFIG_PM_DEBUG
                if (pm_test_level != TEST_NONE && pm_test_level <= TEST_CPUS) {
                        pr_warn("PM: Unsupported test mode for suspend to idle, please choose none/freezer/devices/platform.
");
                        return -EAGAIN;
                }
#endif
//判断该平台是否支持该电源状态,见下面分析
        } else if (!valid_state(state)) {
                return -EINVAL;
        }
//b)加互斥锁,只允许一个实例处理suspend。
        if (!mutex_trylock(&pm_mutex))
                return -EBUSY;
//c)如果state是freeze,调用freeze_begin,进行suspend to freeze相关的特殊动作。我会在后面统一分析freeze的特殊动作,这里暂不描述。
        if (state == PM_SUSPEND_FREEZE)
                freeze_begin();

#ifndef CONFIG_SUSPEND_SKIP_SYNC
        trace_suspend_resume(TPS("sync_filesystems"), 0, true);
        pr_info("2PM: Syncing filesystems ... ");
        sys_sync();
        pr_cont("done.
");
        trace_suspend_resume(TPS("sync_filesystems"), 0, false);
#endif
//d)打印提示信息,同步文件系统。
        pr_debug("PM: Preparing system for sleep (%s)
", pm_states[state]);
        pm_suspend_clear_flags();
//进行suspend前的准备,主要包括switch console和process&thread freezing。
//如果失败,则终止suspend过程。
        error = suspend_prepare(state);
        if (error)
                goto Unlock;

        if (suspend_test(TEST_FREEZER))
                goto Finish;

        trace_suspend_resume(TPS("suspend_enter"), state, false);
        pr_debug("PM: Suspending system (%s)
", pm_states[state]);
        pm_restrict_gfp_mask();
//接口负责suspend和resume的所有实际动作。
//前半部分,suspend console、suspend device、关中断、调用平台相关的suspend_ops使系统进入低功耗状态。
//后半部分,在系统被事件唤醒后,处理相关动作,调用平台相关的suspend_ops恢复系统、开中断、resume device、resume console。
        error = suspend_devices_and_enter(state);
        pm_restore_gfp_mask();

 Finish:
        pr_debug("PM: Finishing wakeup.
");
//调用suspend_finish,恢复(或等待恢复)process&thread,还原console
        suspend_finish();
 Unlock:
        mutex_unlock(&pm_mutex);
        return error;
}

主要工作包括:

3.3.1 valid_state

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

static bool valid_state(suspend_state_t state)
{
//对于standby和mem,则需要调用suspend_ops的valid回掉,由底层平台代码判断是否支持。
    return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
}

对于standby和mem,则需要调用suspend_ops的valid回调,由底层平台代码判断是否支持。suspend_ops->valid(state)对应:

static int imx6q_pm_valid(suspend_state_t state)
{
        return (state == PM_SUSPEND_STANDBY || state == PM_SUSPEND_MEM);
}
static const struct platform_suspend_ops imx6q_pm_ops = {
        .enter = imx6q_pm_enter,
        .valid = imx6q_pm_valid,
};

//赋值流程如下,系统启动的时候会match machtne执行init
DT_MACHINE_START(IMX6Q, "Freescale i.MX6 Quad/DualLite (Device Tree)")
        .l2c_aux_val         = 0,
        .l2c_aux_mask        = ~0,
        .smp                = smp_ops(imx_smp_ops),
        .map_io                = imx6q_map_io,
        .init_irq        = imx6q_init_irq,
        .init_machine        = imx6q_init_machine,
        .init_late      = imx6q_init_late,
        .dt_compat        = imx6q_dt_compat,
MACHINE_END
imx6q_init_machine
--》cpu_is_imx6q() ?  imx6q_pm_init() : imx6dl_pm_init();
--》imx6_pm_common_init(&imx6q_pm_data);
--》ret = imx6q_suspend_init(socdata);
--》suspend_set_ops(&imx6q_pm_ops);

3.3.2 suspend_prepare

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

static int suspend_prepare(suspend_state_t state)
{
        int error, nr_calls = 0;

        if (!sleep_state_supported(state))
                return -EPERM;
 //将当前console切换到一个虚拟console并重定向内核的kmsg(需要的话)
        pm_prepare_console();
 //发送suspend开始的消息
        error = __pm_notifier_call_chain(PM_SUSPEND_PREPARE, -1, &nr_calls);
        if (error) {
                nr_calls--;
                goto Finish;
        }

        trace_suspend_resume(TPS("freeze_processes"), 0, true);
 //freeze用户空间进程和一些内核线程
        error = suspend_freeze_processes();
        trace_suspend_resume(TPS("freeze_processes"), 0, false);
        if (!error)
                return 0;
 //如果freezing-of-tasks失败,调用pm_restore_console,将console切换回原来的console,
 //并返回错误,以便能终止suspend。
        suspend_stats.failed_freeze++;
        dpm_save_failed_step(SUSPEND_FREEZE);
 Finish:
        __pm_notifier_call_chain(PM_POST_SUSPEND, nr_calls, NULL);
        pm_restore_console();
        return error;
}

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

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

suspend_freeze_processes

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

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

        orig_kmsg = vt_kmsg_redirect(SUSPEND_CONSOLE);
        return;
}

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

调用suspend_freeze_processes,freeze用户空间进程和一些内核线程。该功能称作freezing-of-tasks,

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

3.3.3 suspend_devices_and_enter

然后,调用suspend_devices_and_enter接口,该接口负责suspend和resume的所有实际动作。

suspend_freeze_processes冻结进程的主要流程

suspend_freeze_processes
——>    freeze_processes()   -> pm_freezing = true   
                         ->try_to_freeze_tasks ->  freeze_task ->  fake_signal_wake_up(p); 冻结用户进程



->freeze_kernel_threads()  ->pm_nosig_freezing = true 
                           ->try_to_freeze_tasks -> freeze_task ->    freeze_workqueues_begin();   冻结workqueue;
                                                                    wake_up_state(p, task_interruptible); 冻结内核线程

冻结的对象:可以被调度执行的实体,包括用户进程,内核线程和workqueue.

前半部分,suspend console、suspend device、关中断、调用平台相关的suspend_ops使系统进入低功耗状态。

后半部分,在系统被事件唤醒后,处理相关动作,调用平台相关的suspend_ops恢复系统、开中断、resume device、resume console。

int suspend_devices_and_enter(suspend_state_t state)
{
        int error;
        bool wakeup = false;
//检查平台代码是否需要提供以及是否提供了suspend_ops
        if (!sleep_state_supported(state))
                return -ENOSYS;
//调用suspend_ops的begin回调(有的话),通知平台代码,以便让其作相应的处理(需要的话)
        error = platform_suspend_begin(state);
        if (error)
                goto Close;
//挂起console
        suspend_console();
        suspend_test_start();
//调用所有设备的->prepare和->suspend回调函数
        error = dpm_suspend_start(PMSG_SUSPEND);
        if (error) {
                pr_err("PM: Some devices failed to suspend, or early wake event detected
");
                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 && platform_suspend_again(state));

 Resume_devices:
        suspend_test_start();
        dpm_resume_end(PMSG_RESUME);
        suspend_test_finish("resume devices");
        trace_suspend_resume(TPS("resume_console"), state, true);
        resume_console();
        trace_suspend_resume(TPS("resume_console"), state, false);

 Close:
        platform_resume_end(state);
        return error;

 Recover_platform:
        platform_recover(state);
        goto Resume_devices;
}

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

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

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

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

调用dpm_suspend_start,调用所有设备的->prepare和->suspend回调函数,suspend需要正常suspend的设备。suspend device可能失败,需要跳至 Recover_platform,执行recover操作(suspend_ops->recover)。

以上都是suspend前的准备工作,此时,调用suspend_enter接口。

3.3.4 dpm_suspend_start

调用所有设备的->prepare和->suspend回调函数,suspend需要正常suspend的设备。suspend device可能失败,需要跳至 Recover_platform,执行recover操作(suspend_ops->recover)。

3.3.5 suspend_enter

suspend_enter接口使系统进入指定的电源状态。该接口的内容如下:

//该接口处理完后,会通过返回值告知是否enter成功,同时通过wakeup指针,告知调用者,是否有wakeup事件发生,导致电源状态切换失败。
static int suspend_enter(suspend_state_t state, bool *wakeup)
{
        int error;
//通知平台代码,以便让其在即将进行状态切换之时,再做一些处理(需要的话
        error = platform_suspend_prepare(state);
        if (error)
                goto Platform_finish;
//调用所有设备的->suspend_late和->suspend_noirq回调函数
        error = dpm_suspend_late(PMSG_SUSPEND);
        if (error) {
                pr_err("PM: late suspend of devices failed
");
                goto Platform_finish;
        }
//通知平台代码,以便让其在最后关头,再做一些处理(需要的话)
        error = platform_suspend_prepare_late(state);
        if (error)
                goto Devices_early_resume;

        error = dpm_suspend_noirq(PMSG_SUSPEND);
        if (error) {
                pr_err("PM: noirq suspend of devices failed
");
                goto Platform_early_resume;
        }
        error = platform_suspend_prepare_noirq(state);
        if (error)
                goto Platform_wake;

        if (suspend_test(TEST_PLATFORM))
                goto Platform_wake;

//如果是suspend to freeze,执行相应的操作,包括冻结进程、suspended devices(参数为PM_SUSPEND_FREEZE)、cpu进入idle。如果有任何事件使CPU从idle状态退出,跳至Platform_wake处,执行wake操作。
        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;
        }
//禁止所有的非boot cpu
        error = disable_nonboot_cpus();
        if (error || suspend_test(TEST_CPUS))
                goto Enable_cpus;
//关全局中断
        arch_suspend_disable_irqs();
        BUG_ON(!irqs_disabled());
//suspend system core
        error = syscore_suspend();
        if (!error) {
//调用pm_wakeup_pending检查一下,这段时间内,是否有唤醒事件发生,如果有就要终止suspend。
                *wakeup = pm_wakeup_pending();
                if (!(suspend_test(TEST_CORE) || *wakeup)) {
                        trace_suspend_resume(TPS("machine_suspend"),
                                state, true);
//调用suspend_ops的enter回调,进行状态切换 
                        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;
}

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。同样会失败,执行恢复操作即可。system core为系统的一些核心功能,如timer、irq、clk等。

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

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

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

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

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

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

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

3.3.6 suspend_finish

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

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

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

发送suspend结束的通知。

将console切换回原来的。

3.4 唤醒源设置

int suspend_enter(suspend_state_t state, bool *wakeup)  
-->int dpm_suspend_noirq(pm_message_t state)  
 -->void dpm_noirq_begin(void)  
 -->void device_wakeup_arm_wake_irqs(void)  
 -->void dev_pm_arm_wake_irq(struct wake_irq *wirq)  
 -->enable_irq_wake()

首先dpm_suspend_noirq会禁止所有的中断 然后enable_irq_wake设置唤醒源中断,调用途径有两种:

一是先在driver的probe函数中调用dev_pm_set_wake_irq()和device_init_wakeup(),将irq标记为wakeup irq, 这样在如下流程中将标记为wakeup irq的irq为唤醒源:

二是在driver的suspend函数中主动调用调用;即当系统将要进入suspend模式时,会先调用设备的设置suspend接口进入suspend模式,在该过程中会先判断如果该设备被设置为 wakeup source(通过调用device_may_wakeup()判断),则调用enable_irq_wake()将设备对应的irq设置为wakeup 功能的irq。

例如一个GPIO按键,在DTB中,设置属性”wakeup-source“为1,在GPIO驱动中

static struct platform_driver egpio_driver = {
.driver = {
.name = "htc-egpio",
.suppress_bind_attrs = true,
},
.suspend      = egpio_suspend,
.resume       = egpio_resume,
};

static int egpio_suspend(struct platform_device *pdev, pm_message_t state)
{
struct egpio_info *ei = platform_get_drvdata(pdev);

if (ei->chained_irq && device_may_wakeup(&pdev->dev))
enable_irq_wake(ei->chained_irq);
return 0;
}

一般其他驱动例如USB、Ethnet、触摸屏等都会触发唤醒。就是感觉到有人要使用了就会触发。

int suspend_enter(suspend_state_t state, bool *wakeup)  
-->int dpm_suspend_noirq(pm_message_t state)
    -->suspend_device_irqs();
        -->__disable_irq(desc);

为设备中断处理函数,会对每个设备中断执行关闭操作

3.5 struct platform_suspend_ops

这个由平台实现,见代码里面只有enter和valid,不全面

static const struct platform_suspend_ops imx6q_pm_ops = {
        .enter = imx6q_pm_enter,
        .valid = imx6q_pm_valid,
};

正常的操作接口可以操作psci

8bdbf5dc-8456-11ee-939d-92fbcf53809c.png

一般suspend设计power domain,是按域进行实际硬件操作的,这就需要系统支持power domain的驱动,例如 drivers/soc/rockchip/pm_domains.c

static const struct rockchip_domain_info rk3399_pm_domains[] = {
[RK3399_PD_TCPD0]= DOMAIN_RK3399("tcpd0",     BIT(8),  BIT(8),  0,       false),
[RK3399_PD_TCPD1]= DOMAIN_RK3399("tcpd1",     BIT(9),  BIT(9),  0,       false),
[RK3399_PD_CCI]= DOMAIN_RK3399("cci",       BIT(10), BIT(10), 0,       true),
[RK3399_PD_CCI0]= DOMAIN_RK3399("cci0",      0,       0,       BIT(15), true),
[RK3399_PD_CCI1]= DOMAIN_RK3399("cci1",      0,       0,       BIT(16), true),
[RK3399_PD_PERILP]= DOMAIN_RK3399("perilp",    BIT(11), BIT(11), BIT(1),  true),
[RK3399_PD_PERIHP]= DOMAIN_RK3399("perihp",    BIT(12), BIT(12), BIT(2),  true),
[RK3399_PD_CENTER]= DOMAIN_RK3399("center",    BIT(13), BIT(13), BIT(14), true),
[RK3399_PD_VIO]= DOMAIN_RK3399("vio",       BIT(14), BIT(14), BIT(17), false),
[RK3399_PD_GPU]= DOMAIN_RK3399("gpu",       BIT(15), BIT(15), BIT(0),  false),
[RK3399_PD_VCODEC]= DOMAIN_RK3399("vcodec",    BIT(16), BIT(16), BIT(3),  false),
[RK3399_PD_VDU]= DOMAIN_RK3399("vdu",       BIT(17), BIT(17), BIT(4),  false),
[RK3399_PD_RGA]= DOMAIN_RK3399("rga",       BIT(18), BIT(18), BIT(5),  false),
[RK3399_PD_IEP]= DOMAIN_RK3399("iep",       BIT(19), BIT(19), BIT(6),  false),
[RK3399_PD_VO]= DOMAIN_RK3399("vo",        BIT(20), BIT(20), 0,       false),
[RK3399_PD_VOPB]= DOMAIN_RK3399("vopb",      0,       0,       BIT(7),  false),
[RK3399_PD_VOPL]= DOMAIN_RK3399("vopl",      0,       0,       BIT(8),  false),
[RK3399_PD_ISP0]= DOMAIN_RK3399("isp0",      BIT(22), BIT(22), BIT(9),  false),
[RK3399_PD_ISP1]= DOMAIN_RK3399("isp1",      BIT(23), BIT(23), BIT(10), false),
[RK3399_PD_HDCP]= DOMAIN_RK3399("hdcp",      BIT(24), BIT(24), BIT(11), false),
[RK3399_PD_GMAC]= DOMAIN_RK3399("gmac",      BIT(25), BIT(25), BIT(23), true),
[RK3399_PD_EMMC]= DOMAIN_RK3399("emmc",      BIT(26), BIT(26), BIT(24), true),
[RK3399_PD_USB3]= DOMAIN_RK3399("usb3",      BIT(27), BIT(27), BIT(12), true),
[RK3399_PD_EDP]= DOMAIN_RK3399("edp",       BIT(28), BIT(28), BIT(22), false),
[RK3399_PD_GIC]= DOMAIN_RK3399("gic",       BIT(29), BIT(29), BIT(27), true),
[RK3399_PD_SD]= DOMAIN_RK3399("sd",        BIT(30), BIT(30), BIT(28), true),
[RK3399_PD_SDIOAUDIO]= DOMAIN_RK3399("sdioaudio", BIT(31), BIT(31), BIT(29), true),
};

这个驱动在probe的时候会添加这些domain并赋值回调函数

pd->genpd.power_off = rockchip_pd_power_off;
pd->genpd.power_on = rockchip_pd_power_on;

3.6 ATF中处理

8c0f6c6e-8456-11ee-939d-92fbcf53809c.png在ATF代码中,处理smc为

#define PSCI_CPU_SUSPEND_AARCH64U(0xc4000001)

std_svc_smc_handler
--》psci_smc_handler
-->psci_cpu_suspend

#define PSCI_SYSTEM_SUSPEND_AARCH64U(0xc400000E)

int psci_system_suspend(uintptr_t entrypoint, u_register_t context_id)
{
if (!psci_is_last_on_cpu()) //必须是最后一个on的cpu

rc = psci_validate_entry_point(&ep, entrypoint, context_id); //判断entrypoint有效性

psci_query_sys_suspend_pwrstate(&state_info);//对state进行校验

if (psci_find_target_suspend_lvl(&state_info) < PLAT_MAX_PWR_LVL)
return PSCI_E_DENIED;
assert(psci_validate_suspend_req(&state_info, PSTATE_TYPE_POWERDOWN)
== PSCI_E_SUCCESS);
assert(is_local_state_off(
state_info.pwr_domain_state[PLAT_MAX_PWR_LVL]) != 0);

//设置cpu进入suspend
rc = psci_cpu_suspend_start(&ep,
    PLAT_MAX_PWR_LVL,
    &state_info,
    PSTATE_TYPE_POWERDOWN);

return rc;
}

psci_cpu_suspend_start函数里面可以自己看下,最后执行了wfi指令使cpu down

后记

休眠唤醒流程的确很复杂,直接看代码不同平台实现差异很大,看懂也比较难,我们能做的就是知道大致原理,最后还是要通过PMU或者SCP去实现,都是要区分power domain。然后在需求开发的时候,我们调研几个平台的实现就可以自己攒一个实现了。

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    5017

    文章

    18497

    浏览量

    293262
  • 电源管理
    +关注

    关注

    113

    文章

    6051

    浏览量

    141852
  • RAM
    RAM
    +关注

    关注

    8

    文章

    1332

    浏览量

    113942
  • Linux
    +关注

    关注

    87

    文章

    11030

    浏览量

    207260
  • 内存
    +关注

    关注

    8

    文章

    2800

    浏览量

    73125

原文标题:电源管理入门-8 休眠唤醒

文章出处:【微信号:OS与AUTOSAR研究,微信公众号:OS与AUTOSAR研究】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    开关电源基本概念和分析方法

    开关电源基本概念和分析方法
    发表于 08-05 21:27

    EMI的基本概念

    摘 要: 介绍了电磁干扰(EMI)的基本概念、危害及抑制技术,指出了强化管理,发展EMI抑制技术的重要意义。关键词:电磁干扰;抑制技术;EMC标准;管理1 电磁干扰基本概念在复杂的电磁
    发表于 05-30 06:28

    如何实现局部网络的休眠唤醒机制?

    局部网络管理是什么?局部网络(PN)管理的优势有哪些?如何实现局部网络的休眠唤醒机制?
    发表于 04-19 07:42

    镜像面的基本概念

    第七章 开关电源PCB排版解析7.1 镜像面电磁理论中的镜像面概念对设计者掌握开关电源的PCB 排版会有很大的帮助。  下面是镜像面的基本概念
    发表于 10-28 06:48

    化学电源中的基本概念

     化学电源中的基本概念 1,电化学装置:由两个电极和电解质构成。 2,电化学式: 表明活性物质和电解液的组份。例如:铅酸电池的电化
    发表于 11-05 09:29 1082次阅读

    线性电源和开关型电源基本概念

    介绍线性电源和开关型电源基本概念,很不错的一篇介绍性文章。
    发表于 11-13 15:48 2次下载

    基于S3C2440和WindowsCE5.0的平台休眠唤醒方案

    [2]。Windows CE 作为一个广泛应用于嵌入式设备上的操作系统,提供了完善的电源管理功能。其中,休眠唤醒便是一个重要的功能。本文在结合S3C2440硬件基础上分析
    发表于 10-31 15:51 0次下载
    基于S3C2440和WindowsCE5.0的平台<b class='flag-5'>休眠</b><b class='flag-5'>唤醒</b>方案

    MSP430休眠唤醒装置设计详析

    针对矿用救灾指挥装置网络视频服务器采用电池供电工作时间短的缺点, 采用 MSP430 低功耗单片机设计了一种休眠唤醒装置, 实现了网络视频服务器的休眠唤醒功能, 延长了网络视频服务器电
    发表于 04-24 14:27 14次下载
    MSP430<b class='flag-5'>休眠</b><b class='flag-5'>唤醒</b>装置设计详析

    Linux是休眠/唤醒的步骤解析

    在Linux中,休眠主要分三个主要的步骤:(1)冻结用户态进程和内核态任务;(2)调用注册的设备的suspend的回调函数;(3)按照注册顺序休眠核心设备和使CPU进入休眠态。 冻结进程是内核把进程
    的头像 发表于 10-08 09:52 3200次阅读

    单片机休眠唤醒二三事

    想知道单片机休眠如何像吃了德芙一样丝滑么?想让你的单片机产品在合适的时候休眠待机不再失眠么?想让你的单片机项目随叫随醒不再怠惰长眠么?答案-关于单片机休眠唤醒的配置都在这里了
    发表于 12-20 19:00 24次下载
    单片机<b class='flag-5'>休眠</b>与<b class='flag-5'>唤醒</b>二三事

    APM32F103C8T6_RCM_休眠唤醒后,灯概率性闪烁异常

    APM32F103C8T6_RCM_休眠唤醒后,灯概率性闪烁异常
    发表于 11-09 21:03 0次下载
    APM32F103C8T6_RCM_<b class='flag-5'>休眠</b><b class='flag-5'>唤醒</b>后,灯概率性闪烁异常

    网络关闭但ECU没有休眠前如何进行网络唤醒呢?

    最近在做CAN网络管理的工作,发现网络休眠(关闭)后在ECU系统没有休眠/下电前如果又收到了NM报文,ECU的网络没有被重新唤醒(开启)
    的头像 发表于 03-29 09:06 2237次阅读

    ECU系统休眠后通过诊断报文唤醒ECU且唤醒网络

    ECU系统休眠后TJA1043的INH脚处于floating高阻态,系统休眠后通过硬件外部电路下拉到低电平状态/Low-level,ECU系统休眠前把TJA1043的INH脚配置为唤醒
    的头像 发表于 04-04 09:40 6636次阅读

    Linux内核实现内存管理基本概念

    本文概述Linux内核实现内存管理基本概念,在了解基本概念后,逐步展开介绍实现内存管理的相关技术,后面会分多篇进行介绍。
    发表于 06-23 11:56 616次阅读
    Linux内核实现内存<b class='flag-5'>管理</b>的<b class='flag-5'>基本概念</b>

    LIN休眠唤醒及测试心得

    这次我们的介绍主题是LIN休眠唤醒,一起看看标准和差异性,开发和测试的关系,实际的案例分享也来了。
    的头像 发表于 11-23 08:43 793次阅读
    LIN<b class='flag-5'>休眠</b><b class='flag-5'>唤醒</b>及测试心得