0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

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

yzcdx ? 來源: OS與AUTOSAR研究 ? 2023-11-16 16:30 ? 次閱讀

當(dāng)我們不用設(shè)備的時(shí)候,一般需要關(guān)機(jī),用的時(shí)候再開機(jī),這樣有一個(gè)問題,開機(jī)非常的慢,那么有什么方法即省電又可以快速開機(jī)呢?

答案就是休眠喚醒suspend、resume。甚至很多消費(fèi)者設(shè)備例如手機(jī)汽車都是假關(guān)機(jī),其實(shí)還是休眠,這樣用戶體驗(yàn)好啊,隨時(shí)用幾秒就可以喚醒使用,用戶體驗(yàn)才是王道。

休眠喚醒很重要,一般指的是STR(stroe to RAM),其技術(shù)涉及范圍很廣,需求也很多。有電池的設(shè)備可以省電,另外就是電源供電的也可以延長設(shè)備使用壽命。

1.基本概念和框架

1.1 基本概念

STR

一般的嵌入式產(chǎn)品僅僅只實(shí)現(xiàn)了掛起到RAM(也簡稱為s2ram,或常簡稱為STR),即將系統(tǒng)的狀態(tài)保存于內(nèi)存中,并將SDRAM置于自刷新狀態(tài),待用戶按鍵等操作后再重新恢復(fù)系統(tǒng)。

STD

少數(shù)嵌入式Linux系統(tǒng)會實(shí)現(xiàn)掛起到硬盤(簡稱STD),它與掛起到RAM的不同是s2ram并不關(guān)機(jī),STD則把系統(tǒng)的狀態(tài)保持于磁盤,然后關(guān)閉整個(gè)系統(tǒng)。

這些休眠方式,可以通過操作設(shè)備節(jié)點(diǎn)/sys/power/state設(shè)置freeze、standyby、STR(suspend to RAM)和STD(suspend to disk)去實(shí)現(xiàn)。通過寫入”freeze”、”standby”和”mem”,即可觸發(fā)它們。

休眠后,可以通過喚醒源(按鍵、RTC、屏幕、USB拔插等)對系統(tǒng)進(jìn)行喚醒,喚醒源是不休眠的,需要保留下來監(jiān)聽喚醒操作。

1.2 休眠喚醒技術(shù)框架

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

上層service通過wakelock的使用,在系統(tǒng)不需要工作的時(shí)候經(jīng)由power manager利用PM core提供的文件節(jié)點(diǎn)發(fā)起休眠。

PM core實(shí)現(xiàn)power manage的核心邏輯,為上層services提供操作休眠喚醒的相關(guān)接口,通過利用底層相關(guān)的技術(shù)實(shí)現(xiàn)休眠喚醒過程中的cpu hotplug、wakup source enable/disable、設(shè)備的suspend&resume等。

休眠的過程中PM driver會配置、取消喚醒源,調(diào)用設(shè)備的suspend&resume函數(shù),進(jìn)行syscore的suspend&resume操作。

Services

Services部分由兩類service組成,power manager service及普通的app service。其中,power manager service提供了wakelock鎖的create/request/release管理功能,當(dāng)沒有services持有wakelock的話,power manager service會通過往文件節(jié)點(diǎn)/sys/power/state寫mem發(fā)起內(nèi)核的休眠。

PM core

PM core部分提供了wakelock(決定是否發(fā)起休眠)的實(shí)現(xiàn),wakeup_count(用于各services釋放wakelock后,到發(fā)起內(nèi)核休眠的期間是否有喚醒源,從而是否進(jìn)行resume的管理)的實(shí)現(xiàn),suspend的實(shí)現(xiàn)。這三個(gè)功能分別向上層提供了相應(yīng)的文件節(jié)點(diǎn),供上層操作。休眠、喚醒的過程中會涉及到進(jìn)程的freeze&thaw,wakeup source的使能、失能,設(shè)備的休眠、喚醒,power domain的關(guān)、開,cpu的拔、插等功能或框架。相關(guān)代碼如下:

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過程中對進(jìn)程的處理邏輯

PM driver

PM driver部分主要實(shí)現(xiàn)了設(shè)備驅(qū)動的suspend&resume實(shí)現(xiàn),架構(gòu)驅(qū)動(gpio、irq、timer等)低功耗相關(guān)的操作。

//Device PM
drivers/base/power/* 

//Platform dependent PM
include/linux/suspend.h----定義platform dependent PM有關(guān)的操作函數(shù)集
arch/xxx/mach-xxx/xxx.c或者
arch/xxx/plat-xxx/xxx.c----平臺相關(guān)的電源管理操作

suspend&resume過程概述

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

echo mem > /sys/power/state

做如上操作后,整個(gè)函數(shù)調(diào)用流程如下:

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

其中設(shè)置suspend的核心函數(shù)為suspend_enter, 如下:

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

相關(guān)功能代碼見:kernel/power/main.c和suspend.c等文件。

Linux內(nèi)核Suspend總體流程如下:

state_store()->
    pm_suspend()->
        pm_suspend_marker("entry")      ## 1、標(biāo)記進(jìn)入睡眠
        enter_state()->                 ## 2、處理睡眠相關(guān)工作,重點(diǎn)關(guān)注
            sys_sync()                      ## 2.1、同步文件系統(tǒng)
            suspend_prepare()               ## 2.2、準(zhǔn)備進(jìn)入系統(tǒng)睡眠狀態(tài),并凍結(jié)用戶空間進(jìn)程和內(nèi)核線程
            suspend_devices_and_enter()     ## 2.3、休眠外設(shè)并進(jìn)入系統(tǒng)睡眠狀態(tài),該函數(shù)在系統(tǒng)喚醒時(shí)返回
            suspend_finish()                ## 2.4、睡眠結(jié)束并被喚醒
        pm_suspend_marker("exit")       ## 3、標(biāo)記退出睡眠

下面重點(diǎn)介紹suspend_devices_and_enter()函數(shù)的流程:

suspend_devices_and_enter()->
    ## 1、凍結(jié)串口,可以在u-boot傳入no_console_suspend,釋放suspend流程中串口打印
    suspend_console()
    
    ## 2、外設(shè)驅(qū)動suspend
    dpm_suspend_start()->       
        dpm_prepare()->         
            device_prepare()    ## 執(zhí)行設(shè)備電源管理函數(shù)中的prepare函數(shù)
        dpm_suspend()->
            device_suspend()->
                __device_suspend()->
                    dpm_run_callback()->
                        initcall_debug_start()  ## 顯示調(diào)用的各suspend()函數(shù)名等信息,需要打開pm_print_times
                        cb()                    ## 執(zhí)行各.suspend()函數(shù),包括:外設(shè)驅(qū)動,電源域,總線等(重點(diǎn)關(guān)注****)
                        initcall_debug_report() ## 顯示各suspend()函數(shù)返回值和執(zhí)行時(shí)間
    
    ## 3、系統(tǒng)進(jìn)入睡眠狀態(tài),該流程同時(shí)處理喚醒操作
    suspend_enter()->           
        platform_suspend_prepare()
        dpm_suspend_late(PMSG_SUSPEND)->
            device_suspend_late()->             
                __device_suspend_late()->
                    dpm_run_callback()->
                        initcall_debug_start()  ## 顯示調(diào)用的各suspend()函數(shù)名等信息,需要打開pm_print_times
                        cb()                    ## 執(zhí)行各.suspend_late()函數(shù)  (重點(diǎn)關(guān)注****)
                        initcall_debug_report() ## 顯示各.suspend_late()函數(shù)返回值和執(zhí)行時(shí)間
        
        dpm_suspend_noirq(PMSG_SUSPEND)->
            device_suspend_noirq()->
                __device_suspend_noirq()->
                    dpm_run_callback()->
                        initcall_debug_start()  ## 顯示調(diào)用的各suspend()函數(shù)名等信息,需要打開pm_print_times
                        cb()                    ## 執(zhí)行各.suspend_noirq()函數(shù)  (重點(diǎn)關(guān)注****)
                        initcall_debug_report() ## 顯示各.suspend_noirq()函數(shù)返回值和執(zhí)行時(shí)間                    
                    
        disable_nonboot_cpus()          ## 凍結(jié)非啟動cpu
        arch_suspend_disable_irqs()     ## 關(guān)中斷
        syscore_suspend()               ## 執(zhí)行注冊在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. 詳細(xì)分析

3.1 suspend sys節(jié)點(diǎn)入口

在用戶空間執(zhí)行如下操作:

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

系統(tǒng)初始化時(shí)候調(diào)用pm_init函數(shù),在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;

根據(jù)sys節(jié)點(diǎn)的屬性命令規(guī)則,sysfs接口實(shí)現(xiàn)此節(jié)點(diǎn)的實(shí)現(xiàn)代碼為: 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的時(shí)候就會執(zhí)行state_store()函數(shù)

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,已經(jīng)工作在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定義了一個(gè)名稱為state的attribute文件,該文件的store接口為state_store,該接口在lock住autosleep功能后,解析用戶傳入的buffer(freeze、standby or mem),轉(zhuǎn)換成state參數(shù)。state參數(shù)的類型為suspend_state_t,在includelinuxsuspend.h中定義,為電源管理狀態(tài)在內(nèi)核中的表示

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()函數(shù)中:

static int enter_state(suspend_state_t state)
{
        int error;
//確保沒有人試圖將系統(tǒng)置于睡眠狀態(tài)
        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
//判斷該平臺是否支持該電源狀態(tài),見下面分析
        } else if (!valid_state(state)) {
                return -EINVAL;
        }
//b)加互斥鎖,只允許一個(gè)實(shí)例處理suspend。
        if (!mutex_trylock(&pm_mutex))
                return -EBUSY;
//c)如果state是freeze,調(diào)用freeze_begin,進(jìn)行suspend to freeze相關(guān)的特殊動作。我會在后面統(tǒng)一分析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)打印提示信息,同步文件系統(tǒng)。
        pr_debug("PM: Preparing system for sleep (%s)
", pm_states[state]);
        pm_suspend_clear_flags();
//進(jìn)行suspend前的準(zhǔn)備,主要包括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();
//接口負(fù)責(zé)suspend和resume的所有實(shí)際動作。
//前半部分,suspend console、suspend device、關(guān)中斷、調(diào)用平臺相關(guān)的suspend_ops使系統(tǒng)進(jìn)入低功耗狀態(tài)。
//后半部分,在系統(tǒng)被事件喚醒后,處理相關(guān)動作,調(diào)用平臺相關(guān)的suspend_ops恢復(fù)系統(tǒng)、開中斷、resume device、resume console。
        error = suspend_devices_and_enter(state);
        pm_restore_gfp_mask();

 Finish:
        pr_debug("PM: Finishing wakeup.
");
//調(diào)用suspend_finish,恢復(fù)(或等待恢復(fù))process&thread,還原console
        suspend_finish();
 Unlock:
        mutex_unlock(&pm_mutex);
        return error;
}

主要工作包括:

3.3.1 valid_state

調(diào)用valid_state,判斷該平臺是否支持該電源狀態(tài)。suspend的最終目的,是讓系統(tǒng)進(jìn)入可恢復(fù)的掛起狀態(tài),而該功能必須有平臺相關(guān)代碼的參與才能完成,因此內(nèi)核PM Core就提供了一系列的回調(diào)函數(shù)(封裝在platform_suspend_ops中),讓平臺代碼(如arch/arm/mach-xxx/pm.c)實(shí)現(xiàn),然后由PM Core在合適的時(shí)機(jī)調(diào)用。這些回調(diào)函數(shù)包含一個(gè)valid函數(shù),就是用來告知PM Core,支持哪些state。valid_state的實(shí)現(xiàn)

static bool valid_state(suspend_state_t state)
{
//對于standby和mem,則需要調(diào)用suspend_ops的valid回掉,由底層平臺代碼判斷是否支持。
    return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
}

對于standby和mem,則需要調(diào)用suspend_ops的valid回調(diào),由底層平臺代碼判斷是否支持。suspend_ops->valid(state)對應(yīng):

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,
};

//賦值流程如下,系統(tǒng)啟動的時(shí)候會match machtne執(zhí)行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

調(diào)用suspend_prepare,進(jìn)行suspend前的準(zhǔn)備,主要包括switch console和process&thread freezing。如果失敗,則終止suspend過程。suspend_prepare()函數(shù)如下:

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

        if (!sleep_state_supported(state))
                return -EPERM;
 //將當(dāng)前console切換到一個(gè)虛擬console并重定向內(nèi)核的kmsg(需要的話)
        pm_prepare_console();
 //發(fā)送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用戶空間進(jìn)程和一些內(nèi)核線程
        error = suspend_freeze_processes();
        trace_suspend_resume(TPS("freeze_processes"), 0, false);
        if (!error)
                return 0;
 //如果freezing-of-tasks失敗,調(diào)用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回調(diào),沒有的話,返回錯誤。

調(diào)用pm_prepare_console,將當(dāng)前console切換到一個(gè)虛擬console并重定向內(nèi)核的kmsg(需要的話)。該功能稱作VT switch,后面我會在稍微詳細(xì)的介紹一下,但Linux控制臺子系統(tǒng)是相當(dāng)復(fù)雜的,更具體的分析,要在控制臺子系統(tǒng)的分析文章中說明。

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;
}

調(diào)用pm_notifier_call_chain,發(fā)送suspend開始的消息(PM_SUSPEND_PREPARE),后面會詳細(xì)描述。

調(diào)用suspend_freeze_processes,freeze用戶空間進(jìn)程和一些內(nèi)核線程。該功能稱作freezing-of-tasks,

如果freezing-of-tasks失敗,調(diào)用pm_restore_console,將console切換回原來的console,并返回錯誤,以便能終止suspend。

3.3.3 suspend_devices_and_enter

然后,調(diào)用suspend_devices_and_enter接口,該接口負(fù)責(zé)suspend和resume的所有實(shí)際動作。

suspend_freeze_processes凍結(jié)進(jìn)程的主要流程

suspend_freeze_processes
——>    freeze_processes()   -> pm_freezing = true   
                         ->try_to_freeze_tasks ->  freeze_task ->  fake_signal_wake_up(p); 凍結(jié)用戶進(jìn)程



->freeze_kernel_threads()  ->pm_nosig_freezing = true 
                           ->try_to_freeze_tasks -> freeze_task ->    freeze_workqueues_begin();   凍結(jié)workqueue;
                                                                    wake_up_state(p, task_interruptible); 凍結(jié)內(nèi)核線程

凍結(jié)的對象:可以被調(diào)度執(zhí)行的實(shí)體,包括用戶進(jìn)程,內(nèi)核線程和workqueue.

前半部分,suspend console、suspend device、關(guān)中斷、調(diào)用平臺相關(guān)的suspend_ops使系統(tǒng)進(jìn)入低功耗狀態(tài)。

后半部分,在系統(tǒng)被事件喚醒后,處理相關(guān)動作,調(diào)用平臺相關(guān)的suspend_ops恢復(fù)系統(tǒng)、開中斷、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;
//調(diào)用suspend_ops的begin回調(diào)(有的話),通知平臺代碼,以便讓其作相應(yīng)的處理(需要的話)
        error = platform_suspend_begin(state);
        if (error)
                goto Close;
//掛起console
        suspend_console();
        suspend_test_start();
//調(diào)用所有設(shè)備的->prepare和->suspend回調(diào)函數(shù)
        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。

調(diào)用suspend_ops的begin回調(diào)(有的話),通知平臺代碼,以便讓其作相應(yīng)的處理(需要的話)。可能失敗,需要跳至Close處執(zhí)行恢復(fù)操作(suspend_ops->end)。

調(diào)用suspend_console,掛起console。該接口由"kernelprintk.c"實(shí)現(xiàn),主要是hold住一個(gè)lock,該lock會阻止其它代碼訪問console。

調(diào)用ftrace_stop,停止ftrace功能。ftrace是一個(gè)很有意思的功能,后面再介紹。

調(diào)用dpm_suspend_start,調(diào)用所有設(shè)備的->prepare和->suspend回調(diào)函數(shù),suspend需要正常suspend的設(shè)備。suspend device可能失敗,需要跳至 Recover_platform,執(zhí)行recover操作(suspend_ops->recover)。

以上都是suspend前的準(zhǔn)備工作,此時(shí),調(diào)用suspend_enter接口。

3.3.4 dpm_suspend_start

調(diào)用所有設(shè)備的->prepare和->suspend回調(diào)函數(shù),suspend需要正常suspend的設(shè)備。suspend device可能失敗,需要跳至 Recover_platform,執(zhí)行recover操作(suspend_ops->recover)。

3.3.5 suspend_enter

suspend_enter接口使系統(tǒng)進(jìn)入指定的電源狀態(tài)。該接口的內(nèi)容如下:

//該接口處理完后,會通過返回值告知是否enter成功,同時(shí)通過wakeup指針,告知調(diào)用者,是否有wakeup事件發(fā)生,導(dǎo)致電源狀態(tài)切換失敗。
static int suspend_enter(suspend_state_t state, bool *wakeup)
{
        int error;
//通知平臺代碼,以便讓其在即將進(jìn)行狀態(tài)切換之時(shí),再做一些處理(需要的話
        error = platform_suspend_prepare(state);
        if (error)
                goto Platform_finish;
//調(diào)用所有設(shè)備的->suspend_late和->suspend_noirq回調(diào)函數(shù)
        error = dpm_suspend_late(PMSG_SUSPEND);
        if (error) {
                pr_err("PM: late suspend of devices failed
");
                goto Platform_finish;
        }
//通知平臺代碼,以便讓其在最后關(guān)頭,再做一些處理(需要的話)
        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,執(zhí)行相應(yīng)的操作,包括凍結(jié)進(jìn)程、suspended devices(參數(shù)為PM_SUSPEND_FREEZE)、cpu進(jìn)入idle。如果有任何事件使CPU從idle狀態(tài)退出,跳至Platform_wake處,執(zhí)行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;
//關(guān)全局中斷
        arch_suspend_disable_irqs();
        BUG_ON(!irqs_disabled());
//suspend system core
        error = syscore_suspend();
        if (!error) {
//調(diào)用pm_wakeup_pending檢查一下,這段時(shí)間內(nèi),是否有喚醒事件發(fā)生,如果有就要終止suspend。
                *wakeup = pm_wakeup_pending();
                if (!(suspend_test(TEST_CORE) || *wakeup)) {
                        trace_suspend_resume(TPS("machine_suspend"),
                                state, true);
//調(diào)用suspend_ops的enter回調(diào),進(jìn)行狀態(tài)切換 
                        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成功,同時(shí)通過wakeup指針,告知調(diào)用者,是否有wakeup事件發(fā)生,導(dǎo)致電源狀態(tài)切換失敗。

f2)調(diào)用suspend_ops的prepare回調(diào)(有的話),通知平臺代碼,以便讓其在即將進(jìn)行狀態(tài)切換之時(shí),再做一些處理(需要的話)。該回調(diào)可能失?。ㄆ脚_代碼出現(xiàn)意外),失敗的話,需要跳至Platform_finish處,調(diào)用suspend_ops的finish回調(diào),執(zhí)行恢復(fù)操作。

f3)調(diào)用dpm_suspend_end,調(diào)用所有設(shè)備的->suspend_late和->suspend_noirq回調(diào)函數(shù)(具體可參考“Linux電源管理(4)_Power Management Interface”的描述),suspend late suspend設(shè)備和需要在關(guān)中斷下suspend的設(shè)備。需要說明的是,這里的noirq,是通過禁止所有的中斷線的形式,而不是通過關(guān)全局中斷的方式。同樣,該操作可能會失敗,失敗的話,跳至Platform_finish處,執(zhí)行恢復(fù)動作。

f4)調(diào)用suspend_ops的prepare_late回調(diào)(有的話),通知平臺代碼,以便讓其在最后關(guān)頭,再做一些處理(需要的話)。該回調(diào)可能失敗(平臺代碼出現(xiàn)意外),失敗的話,需要跳至Platform_wake處,調(diào)用suspend_ops的wake回調(diào),執(zhí)行device的resume、調(diào)用suspend_ops的finish回調(diào),執(zhí)行恢復(fù)操作。

f5)如果是suspend to freeze,執(zhí)行相應(yīng)的操作,包括凍結(jié)進(jìn)程、suspended devices(參數(shù)為PM_SUSPEND_FREEZE)、cpu進(jìn)入idle。如果有任何事件使CPU從idle狀態(tài)退出,跳至Platform_wake處,執(zhí)行wake操作。

f6)調(diào)用disable_nonboot_cpus,禁止所有的非boot cpu。也會失敗,執(zhí)行恢復(fù)操作即可。

f7)調(diào)用arch_suspend_disable_irqs,關(guān)全局中斷。如果無法關(guān)閉,則為bug。

f8)調(diào)用syscore_suspend,suspend system core。同樣會失敗,執(zhí)行恢復(fù)操作即可。system core為系統(tǒng)的一些核心功能,如timer、irq、clk等。

f9)如果很幸運(yùn),以上操作都成功了,那么,切換吧。不過,別高興太早,還得調(diào)用pm_wakeup_pending檢查一下,這段時(shí)間內(nèi),是否有喚醒事件發(fā)生,如果有就要終止suspend。

f10)如果一切順利,調(diào)用suspend_ops的enter回調(diào),進(jìn)行狀態(tài)切換。這時(shí),系統(tǒng)應(yīng)該已經(jīng)suspend了……

f11)suspend過程中,喚醒事件發(fā)生,系統(tǒng)喚醒,該函數(shù)接著執(zhí)行resume動作,并最終返回。resume動作基本上是suspend的反動作,就不再繼續(xù)分析了。

f12)或者,由于意外,suspend終止,該函數(shù)也會返回。

suspend_enter返回,如果返回原因不是發(fā)生錯誤,且不是wakeup事件。則調(diào)用suspend_ops的suspend_again回調(diào),檢查是否需要再次suspend。再什么情況下要再次suspend呢?需要看具體的平臺了,誰知道呢。

繼續(xù)resume操作,resume device、start ftrace、resume console、suspend_ops->end等等。

該函數(shù)返回后,表示系統(tǒng)已經(jīng)resume。

3.3.6 suspend_finish

最后,調(diào)用suspend_finish,恢復(fù)(或等待恢復(fù))process&thread,還原console。

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

恢復(fù)所有的用戶空間進(jìn)程和內(nèi)核線程。

發(fā)送suspend結(jié)束的通知。

將console切換回原來的。

3.4 喚醒源設(shè)置

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設(shè)置喚醒源中斷,調(diào)用途徑有兩種:

一是先在driver的probe函數(shù)中調(diào)用dev_pm_set_wake_irq()和device_init_wakeup(),將irq標(biāo)記為wakeup irq, 這樣在如下流程中將標(biāo)記為wakeup irq的irq為喚醒源:

二是在driver的suspend函數(shù)中主動調(diào)用調(diào)用;即當(dāng)系統(tǒng)將要進(jìn)入suspend模式時(shí),會先調(diào)用設(shè)備的設(shè)置suspend接口進(jìn)入suspend模式,在該過程中會先判斷如果該設(shè)備被設(shè)置為 wakeup source(通過調(diào)用device_may_wakeup()判斷),則調(diào)用enable_irq_wake()將設(shè)備對應(yīng)的irq設(shè)置為wakeup 功能的irq。

例如一個(gè)GPIO按鍵,在DTB中,設(shè)置屬性”wakeup-source“為1,在GPIO驅(qū)動中

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;
}

一般其他驅(qū)動例如USB、Ethnet、觸摸屏等都會觸發(fā)喚醒。就是感覺到有人要使用了就會觸發(fā)。

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

為設(shè)備中斷處理函數(shù),會對每個(gè)設(shè)備中斷執(zhí)行關(guān)閉操作

3.5 struct platform_suspend_ops

這個(gè)由平臺實(shí)現(xiàn),見代碼里面只有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設(shè)計(jì)power domain,是按域進(jìn)行實(shí)際硬件操作的,這就需要系統(tǒng)支持power domain的驅(qū)動,例如 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),
};

這個(gè)驅(qū)動在probe的時(shí)候會添加這些domain并賦值回調(diào)函數(shù)

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()) //必須是最后一個(gè)on的cpu

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

psci_query_sys_suspend_pwrstate(&state_info);//對state進(jìn)行校驗(yàn)

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

//設(shè)置cpu進(jìn)入suspend
rc = psci_cpu_suspend_start(&ep,
    PLAT_MAX_PWR_LVL,
    &state_info,
    PSTATE_TYPE_POWERDOWN);

return rc;
}

psci_cpu_suspend_start函數(shù)里面可以自己看下,最后執(zhí)行了wfi指令使cpu down

后記

休眠喚醒流程的確很復(fù)雜,直接看代碼不同平臺實(shí)現(xiàn)差異很大,看懂也比較難,我們能做的就是知道大致原理,最后還是要通過PMU或者SCP去實(shí)現(xiàn),都是要區(qū)分power domain。然后在需求開發(fā)的時(shí)候,我們調(diào)研幾個(gè)平臺的實(shí)現(xiàn)就可以自己攢一個(gè)實(shí)現(xiàn)了。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 嵌入式
    +關(guān)注

    關(guān)注

    5060

    文章

    18979

    瀏覽量

    302236
  • 電源管理
    +關(guān)注

    關(guān)注

    115

    文章

    6143

    瀏覽量

    144120
  • RAM
    RAM
    +關(guān)注

    關(guān)注

    8

    文章

    1365

    瀏覽量

    114476
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11213

    瀏覽量

    208736
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    2982

    瀏覽量

    73826

原文標(biāo)題:電源管理入門-8 休眠喚醒

文章出處:【微信號:OS與AUTOSAR研究,微信公眾號:OS與AUTOSAR研究】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    干貨分享 | TSMaster—LIN 喚醒休眠機(jī)制

    在汽車總線中常見的喚醒方式有硬線喚醒、網(wǎng)絡(luò)喚醒和特定信號喚醒,而LIN總線則是通過休眠幀與喚醒
    的頭像 發(fā)表于 09-25 08:03 ?1276次閱讀
    干貨分享 | TSMaster—LIN <b class='flag-5'>喚醒</b>與<b class='flag-5'>休眠</b>機(jī)制

    開關(guān)電源基本概念和分析方法

    開關(guān)電源基本概念和分析方法
    發(fā)表于 08-05 21:27

    EMI的基本概念

    摘 要: 介紹了電磁干擾(EMI)的基本概念、危害及抑制技術(shù),指出了強(qiáng)化管理,發(fā)展EMI抑制技術(shù)的重要意義。關(guān)鍵詞:電磁干擾;抑制技術(shù);EMC標(biāo)準(zhǔn);管理1 電磁干擾基本概念在復(fù)雜的電磁
    發(fā)表于 05-30 06:28

    如何實(shí)現(xiàn)局部網(wǎng)絡(luò)的休眠喚醒機(jī)制?

    局部網(wǎng)絡(luò)管理是什么?局部網(wǎng)絡(luò)(PN)管理的優(yōu)勢有哪些?如何實(shí)現(xiàn)局部網(wǎng)絡(luò)的休眠喚醒機(jī)制?
    發(fā)表于 04-19 07:42

    鏡像面的基本概念

    第七章 開關(guān)電源PCB排版解析7.1 鏡像面電磁理論中的鏡像面概念對設(shè)計(jì)者掌握開關(guān)電源的PCB 排版會有很大的幫助。  下面是鏡像面的基本概念
    發(fā)表于 10-28 06:48

    內(nèi)存的基本概念以及操作系統(tǒng)的內(nèi)存管理算法

    本文主要介紹內(nèi)存的基本概念以及操作系統(tǒng)的內(nèi)存管理算法。內(nèi)存的基本概念內(nèi)存是計(jì)算機(jī)系統(tǒng)中除了處理器以外最重要的資源,用于存儲當(dāng)前正在執(zhí)行的程序和數(shù)據(jù)。內(nèi)存是相對于CPU來說的,CPU可以直接尋址
    發(fā)表于 01-27 06:08

    化學(xué)電源中的基本概念

     化學(xué)電源中的基本概念 1,電化學(xué)裝置:由兩個(gè)電極和電解質(zhì)構(gòu)成。 2,電化學(xué)式: 表明活性物質(zhì)和電解液的組份。例如:鉛酸電池的電化
    發(fā)表于 11-05 09:29 ?1121次閱讀

    線性電源和開關(guān)型電源基本概念

    介紹線性電源和開關(guān)型電源基本概念,很不錯的一篇介紹性文章。
    發(fā)表于 11-13 15:48 ?2次下載

    基于S3C2440和WindowsCE5.0的平臺休眠喚醒方案

    ]。Windows CE 作為一個(gè)廣泛應(yīng)用于嵌入式設(shè)備上的操作系統(tǒng),提供了完善的電源管理功能。其中,休眠喚醒便是一個(gè)重要的功能。本文在結(jié)合S3C2440硬件基礎(chǔ)上分析
    發(fā)表于 10-31 15:51 ?0次下載
    基于S3C2440和WindowsCE5.0的平臺<b class='flag-5'>休眠</b><b class='flag-5'>喚醒</b>方案

    Linux是休眠/喚醒的步驟解析

    在Linux中,休眠主要分三個(gè)主要的步驟:(1)凍結(jié)用戶態(tài)進(jìn)程和內(nèi)核態(tài)任務(wù);(2)調(diào)用注冊的設(shè)備的suspend的回調(diào)函數(shù);(3)按照注冊順序休眠核心設(shè)備和使CPU進(jìn)入休眠態(tài)。 凍結(jié)進(jìn)程是內(nèi)核把進(jìn)程
    的頭像 發(fā)表于 10-08 09:52 ?3387次閱讀

    單片機(jī)休眠喚醒二三事

    想知道單片機(jī)休眠如何像吃了德芙一樣絲滑么?想讓你的單片機(jī)產(chǎn)品在合適的時(shí)候休眠待機(jī)不再失眠么?想讓你的單片機(jī)項(xiàng)目隨叫隨醒不再怠惰長眠么?答案-關(guān)于單片機(jī)休眠喚醒的配置都在這里了
    發(fā)表于 12-20 19:00 ?24次下載
    單片機(jī)<b class='flag-5'>休眠</b>與<b class='flag-5'>喚醒</b>二三事

    網(wǎng)絡(luò)關(guān)閉但ECU沒有休眠前如何進(jìn)行網(wǎng)絡(luò)喚醒呢?

    最近在做CAN網(wǎng)絡(luò)管理的工作,發(fā)現(xiàn)網(wǎng)絡(luò)休眠(關(guān)閉)后在ECU系統(tǒng)沒有休眠/下電前如果又收到了NM報(bào)文,ECU的網(wǎng)絡(luò)沒有被重新喚醒(開啟)
    的頭像 發(fā)表于 03-29 09:06 ?2850次閱讀

    ECU系統(tǒng)休眠后通過診斷報(bào)文喚醒ECU且喚醒網(wǎng)絡(luò)

    ECU系統(tǒng)休眠后TJA1043的INH腳處于floating高阻態(tài),系統(tǒng)休眠后通過硬件外部電路下拉到低電平狀態(tài)/Low-level,ECU系統(tǒng)休眠前把TJA1043的INH腳配置為喚醒
    的頭像 發(fā)表于 04-04 09:40 ?8077次閱讀

    Linux內(nèi)核實(shí)現(xiàn)內(nèi)存管理基本概念

    本文概述Linux內(nèi)核實(shí)現(xiàn)內(nèi)存管理基本概念,在了解基本概念后,逐步展開介紹實(shí)現(xiàn)內(nèi)存管理的相關(guān)技術(shù),后面會分多篇進(jìn)行介紹。
    發(fā)表于 06-23 11:56 ?787次閱讀
    Linux內(nèi)核實(shí)現(xiàn)內(nèi)存<b class='flag-5'>管理</b>的<b class='flag-5'>基本概念</b>

    LIN休眠喚醒及測試心得

    這次我們的介紹主題是LIN休眠喚醒,一起看看標(biāo)準(zhǔn)和差異性,開發(fā)和測試的關(guān)系,實(shí)際的案例分享也來了。
    的頭像 發(fā)表于 11-23 08:43 ?1085次閱讀
    LIN<b class='flag-5'>休眠</b><b class='flag-5'>喚醒</b>及測試心得