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的流程分析就到此结束了。

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