當(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ù)框架
上層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過程概述
2. 核心代碼分析
echo mem > /sys/power/state
做如上操作后,整個(gè)函數(shù)調(diào)用流程如下:
其中設(shè)置suspend的核心函數(shù)為suspend_enter, 如下:
相關(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
一般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中處理
在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)了。
-
嵌入式
+關(guān)注
關(guān)注
5060文章
18979瀏覽量
302236 -
電源管理
+關(guān)注
關(guān)注
115文章
6143瀏覽量
144120 -
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)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論