Linux系統的suspend流程分析

第1和第2節的參考鏈接:

http://www.wowotech.net/pm_subsystem/suspend_and_resume.html

第3節開始的參考鏈接:

https://www.cnblogs.com/arnoldlu/p/6344847.html

https://blog.csdn.net/tiantao2012/article/details/72621155

https://www.cnblogs.com/LoyenWang/p/11370557.html

正文

1、

Linux內核提供了三種Suspend: Freeze、Standby和STR(Suspend to RAM),在用戶空間向"/sys/power/state”文件分別寫入”freeze”、”standby”和”mem”,即可觸發它們。在用戶空間執行如下操作會調用到內核空間的相關函數:

echo "freeze" > /sys/power/state  /*Freeze*/

echo "standby" > /sys/power/state /*Standby*/

echo "mem" > /sys/power/state /*STR*/

2、

假如向節點/sys/power/state寫入了mem,我們看一下具體怎麼調用的

函數路徑:

state_store:kernel\power\main.c

pm_suspend、valid_state:kernel\power\suspend.c

函數調用關係:

state_store
    pm_suspend
        enter_state
            valid_state  ---- 2.1節
                sys_sync /*同步文件系統*/
                suspend_prepare -----  2.2節
                suspend_devices_and_enter -----  2.3節
                suspend_finish

2.1、

static bool valid_state(suspend_state_t state)
{
    /*
     * PM_SUSPEND_STANDBY and PM_SUSPEND_MEM states need low level
     * support and need to be valid to the low level
     * implementation, no valid callback implies that none are valid.
     */
    return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
}

實際上,valid_state函數是判斷該平臺是否支持該state的待機方式,而suspend_ops這個結構體是該平臺自己實現並註冊進系統的,比如我們amlogic平臺的相關代碼。

函數路徑:

meson_pm_probe:drivers\amlogic\pm\gx_pm.c
suspend_set_ops:kernel\power\suspend.c
static const struct platform_suspend_ops meson_gx_ops = {
    .enter = meson_gx_enter,
    .prepare = meson_pm_prepare,
    .finish = meson_pm_finish,
    .valid = suspend_valid_only_mem,
};

static int __init meson_pm_probe(struct platform_device *pdev)
{
    ...
    suspend_set_ops(&meson_gx_ops);
    ...
}

而suspend_set_ops函數就將meson_gx_ops賦值給了suspend_ops結構體。

2.2、

代碼路徑:

suspend_prepare:kernel\power\suspend.c

函數調用關係:

suspend_prepare
    sleep_state_supported /* 檢查是否提供了.enter回調函數,這個回調函數也是由平臺實現 */

2.3、

代碼路徑:

kernel\power\suspend.c

函數調用關係:

suspend_devices_and_enter
    sleep_state_supported /*再次檢查平臺是否支持suspend_ops*/
    platform_suspend_begin /*如果是freeze狀態,則調用平臺註冊的freeze_ops->begin回調函數;或則suspend_ops->begin;否則返回0*/
    suspend_console /*掛起console。該接口由"kernel\printk.c"實現,主要是hold住一個lock,該lock會阻止其它代碼訪問console*/
    dpm_suspend_start /*調用所有設備的->prepare和->suspend回調函數,suspend需要正常suspend的設備。suspend device可能失敗,需要跳至 Recover_platform,執行recover操作*/
    suspend_enter ----- 2.3.1節

suspend_enter調用前,都是suspend前的準備工作,調用suspend_enter接口,使系統進入指定的電源狀態。

2.3.1、

代碼路徑:

kernel\power\suspend.c

函數調用關係:

suspend_enter
    platform_suspend_prepare /*回調平臺的suspend_ops->prepare函數,我的平臺只是打印一些信息,沒有實際動作*/
    dpm_suspend_late /*調用所有設備的->suspend_late*/
    platform_suspend_prepare_late /*如果是freeze狀態,則調用平臺註冊的freeze_ops->prepare回調函數*/
    dpm_suspend_noirq /*調用所有設備的->suspend_noirq*/
    platform_suspend_prepare_noirq /*如果非freeze狀態,並且實現了suspend_ops->prepare_late函數,則調用*/
    disable_nonboot_cpus /*禁止所有的非boot cpu,啓動過程中,負責啓動系統給的CPU稱爲boot CPU。也會失敗,執行恢復操作即可*/
    arch_suspend_disable_irqs /*實際調用local_irq_disable,關全局中斷。如果無法關閉,則爲bug*/
    pm_wakeup_pending /*最後檢查一下是否還有喚醒事件發生*/
    suspend_ops->enter /*一切順利的話,就調用到enter回調函數進入suspend了*/

3、

上面最後調用到suspend_ops->enter,不同的平臺enter函數可能不一樣,在上面講到,是在suspend_set_ops時註冊進系統的。

下面主要分析我手上的平臺

代碼路徑:

meson_gx_enter、meson_gx_suspend:drivers\amlogic\pm\gx_pm.c
arm_cpuidle_suspend:arch\arm64\kernel\cpuidle.c

函數調用關係:

meson_gx_enter
    meson_gx_suspend
        arm_cpuidle_suspend
            int cpu = smp_processor_id();  /*獲取當前是哪個CPU core*/
            return cpu_ops[cpu]->cpu_suspend(index); /*調用cpu_suspend函數*/

3.1、

上面我們可能又要遇到一個難題,這個cpu_suspend函數具體是哪個呢?很明顯,這個是回調函數,想知道具體調用到哪裏去了,還需要從系統初始化講起。直接給出函數調用流程,對着代碼看就一目瞭然了。

代碼路徑:

start_kernel:init\main.c
setup_arch:arch\arm64\kernel\setup.c
psci_dt_init:

函數調用關係:

start_kernel
    setup_arch
        psci_dt_init  ---  3.1.1小節

3.1.1、

這小節我們看一下psci_dt_init函數具體做了什麼

static const struct of_device_id psci_of_match[] __initconst = {
    { .compatible = "arm,psci",    .data = psci_0_1_init},
    { .compatible = "arm,psci-0.2",    .data = psci_0_2_init},
    { .compatible = "arm,psci-1.0",    .data = psci_0_2_init},
    {},
};

int __init psci_dt_init(void)
{
    ...
    np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np);
    
    if (!np)
        return -ENODEV;

    init_fn = (psci_initcall_t)matched_np->data;
    return init_fn(np);
}

psci_dt_init中調用了of_find_matching_node_and_match去解析psci_of_match結構體,並找到匹配的compatible字段。比如我的

平臺dts文件中有如下定義:

    psci {
        compatible = "arm,psci-0.2";
        method = "smc";
    };

所以最終調用了第二項的psci_0_2_init函數,下面我們繼續分析psci_0_2_init函數。

3.2、

代碼路徑:

drivers\firmware\psci.c

函數調用關係:

psci_0_2_init
    psci_probe
        psci_get_version /* 獲取psci的版本號,我們平臺使用的是0.2版本 */
        psci_0_2_set_functions ---- 3.2.1小節

        
3.2.1、

代碼很容易,就是回調函數的賦值,從下面可以看出來,.cpu_suspend對應的回調函數是psci_cpu_suspend

static void __init psci_0_2_set_functions(void)
{
    pr_info("Using standard PSCI v0.2 function IDs\n");
    psci_ops.get_version = psci_get_version;

    psci_function_id[PSCI_FN_CPU_SUSPEND] =
                    PSCI_FN_NATIVE(0_2, CPU_SUSPEND);  /* 將最終調用的suspend函數的地址賦值到psci_function_id數組中,後面還會講到 */
    psci_ops.cpu_suspend = psci_cpu_suspend;
    
    ...
}

講到這裏,我們應該可以回答3.1小節提到的問題了。系統suspend的最後調用的:cpu_ops[cpu]->cpu_suspend(index)。應該就是對應到psci_cpu_suspend()函數了。但是我們可以發現一個小細節,cpu_suspend是指針cpu_ops的成員,但是psci_cpu_suspend卻是賦值給了psci_ops指針的成員cpu_suspend。又怎麼將cpu_ops->cpu_suspend和psci_ops.cpu_suspend聯繫起來呢?

3.3、

要回答上一小節提出的問題,我們還是直接給出函數調用關係,對着代碼看就一目瞭然了,這個不需要研究,知道流程就可以了。

代碼路徑:

smp_init_cpus、smp_init_cpus:arch\arm64\kernel\smp.c
cpu_read_ops、cpu_read_enable_method、cpu_get_ops:arch\arm64\kernel\cpu_ops.c

函數流程:

start_kernel
    setup_arch
        smp_init_cpus
            smp_cpu_setup
                cpu_read_ops
                    cpu_read_enable_method /* 同樣是獲取DTS的值,這裏返回了"psci" */
                    cpu_get_ops  ---- 3.3.1小節

3.3.1、

static const struct cpu_operations *dt_supported_cpu_ops[] __initconst = {
    &smp_spin_table_ops,
    &cpu_psci_ops,
    NULL,
};

static const struct cpu_operations * __init cpu_get_ops(const char *name)
{
    const struct cpu_operations **ops;

    ops = acpi_disabled ? dt_supported_cpu_ops : acpi_supported_cpu_ops; /* 調用到dt_supported_cpu_ops */

    while (*ops) {
        if (!strcmp(name, (*ops)->name)) /* 因爲我們的是psci,所以調用cpu_psci_ops方法 */
            return *ops;

        ops++;
    }

    return NULL;
}

而cpu_psci_ops結構體的定義是在:arch\arm64\kernel\psci.c

const struct cpu_operations cpu_psci_ops = {
    .name        = "psci",
#ifdef CONFIG_CPU_IDLE
    .cpu_init_idle    = psci_cpu_init_idle,
    .cpu_suspend    = psci_cpu_suspend_enter,
#endif
    .cpu_init    = cpu_psci_cpu_init,
    .cpu_prepare    = cpu_psci_cpu_prepare,
    .cpu_boot    = cpu_psci_cpu_boot,
#ifdef CONFIG_HOTPLUG_CPU
    .cpu_disable    = cpu_psci_cpu_disable,
    .cpu_die    = cpu_psci_cpu_die,
    .cpu_kill    = cpu_psci_cpu_kill,
#endif
};

上面的psci_cpu_suspend_enter就是對應3.2.1小節最後提到的cpu_ops->cpu_suspend,進入到psci_cpu_suspend_enter函數看:

int psci_cpu_suspend_enter(unsigned long index)
{
    int ret;
    u32 *state = __this_cpu_read(psci_power_state); /* 讀取當前CPU的power state,這個值也是在DTS中定義 */
    /*
     * idle state index 0 corresponds to wfi, should never be called
     * from the cpu_suspend operations
     */
    if (WARN_ON_ONCE(!index))
        return -EINVAL;

    if (!psci_power_state_loses_context(state[index - 1]))
        ret = psci_ops.cpu_suspend(state[index - 1], 0);
    else
        ret = cpu_suspend(index, psci_suspend_finisher); /* 這個最後也會調用psci_ops.cpu_suspend */

    return ret;
}

現在一目瞭然了,psci_cpu_suspend_enter裏面最終就是調用到了psci_ops.cpu_suspend函數,也就是3.2.1小節賦值的psci_cpu_suspend函數

4、小結

之所以有這篇文章,主要是之前遇到了一個待機的問題,在跟蹤問題的過程中,跟了一遍代碼流程,後面會繼續寫一篇文章說一下這個問題。而suspend的流程分析就到此結束了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章