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