Linux系统的suspend流程分析
第1和第2节的参考链接:http://www.wowotech.net/pm_subsystem/suspend_and_resume.html第3节开始的参考链接:https://www.cnblogs.com/arnoldlu/p/6344847.htmlhttps://blog.csdn.net/tiantao2012/article/details/72621155ht...
第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的流程分析就到此结束了。
更多推荐
所有评论(0)