前言
最近遇到一個待機問題,系統一直無法正常suspend,跟了一下代碼,發現在PSCI的最後階段返回了一個錯誤碼。這篇文章我們是延續上一篇《Linux系統的suspend流程分析》,繼續揭開psci的神祕面紗。
正文
從《Linux系統的suspend流程分析》一文我們可以看到,suspend的最後會調用到下面的函數
static int psci_cpu_suspend(u32 state, unsigned long entry_point)
{
int err;
u32 fn;
fn = psci_function_id[PSCI_FN_CPU_SUSPEND]; /* 對應到ATF(arm trusted firmware)的CPU_SUSPEND函數 */
err = invoke_psci_fn(fn, state, entry_point, 0); /* 調用CPU_SUSPEND函數 */
return psci_to_linux_errno(err); /* 返回錯誤碼 */
}
psci_cpu_suspend第一個參數state,對應的是DTS文件中的“arm,psci-suspend-param”值:
idle-states {
entry-method = "arm,psci";
SYSTEM_SLEEP_0: system-sleep-0 {
compatible = "arm,idle-state";
arm,psci-suspend-param = <0x0020000>; /* 對應power state */
local-timer-stop;
entry-latency-us = <0x3fffffff>;
exit-latency-us = <0x40000000>;
min-residency-us = <0xffffffff>;
};
};
正常情況下,invoke_psci_fn返回的是PSCI_RET_SUCCESS,如下所示。但是我遇到的問題卻返回了PSCI_RET_INVALID_PARAMS。
static int psci_to_linux_errno(int errno)
{
switch (errno) {
case PSCI_RET_SUCCESS:
return 0;
case PSCI_RET_NOT_SUPPORTED:
return -EOPNOTSUPP;
case PSCI_RET_INVALID_PARAMS:
case PSCI_RET_INVALID_ADDRESS:
return -EINVAL;
case PSCI_RET_DENIED:
return -EPERM;
};
return -EINVAL;
}
爲了解釋爲什麼返回了PSCI_RET_INVALID_PARAMS,我們繼續跟一下suspend流程。psci_function_id[PSCI_FN_CPU_SUSPEND]對應的回調函數如下:
static void __init psci_0_2_set_functions(void)
{
...
psci_function_id[PSCI_FN_CPU_SUSPEND] =
PSCI_FN_NATIVE(0_2, CPU_SUSPEND);
psci_ops.cpu_suspend = psci_cpu_suspend;
...
}
PSCI_FN_NATIVE的宏定義如下
#ifdef CONFIG_64BIT
#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN64_##name
#else
#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN_##name
#endif
因爲我的是64位的,所以最後映射爲:PSCI_0_2_FN64_CPU_SUSPEND。它的定義在include\uapi\linux\psci.h找到了原形。
/* PSCI v0.2 interface */
#define PSCI_0_2_FN_BASE 0x84000000
#define PSCI_0_2_64BIT 0x40000000
#define PSCI_0_2_FN64_BASE \
(PSCI_0_2_FN_BASE + PSCI_0_2_64BIT)
#define PSCI_0_2_FN64(n) (PSCI_0_2_FN64_BASE + (n))
#define PSCI_0_2_FN64_CPU_SUSPEND PSCI_0_2_FN64(1)
最終我們可以得到:
psci_function_id[PSCI_FN_CPU_SUSPEND] = 0xC4000001
如果不瞭解PSCI規範的人,可以會對這個地址值很疑惑,我們可以看一下PSCI規範中的定義:
Power_State_Coordination_Interface_PDD_v1_1_DEN0022D.pdf
說白了,就是規定了0xC4000001這個值對應的是arm trusted firmware中的CPU_SUSPEND。可能很多人也不明白什麼是ATF,可以參考下面的鏈接:
https://blog.csdn.net/shuaifengyun/article/details/72468109
現在armv8架構的待機流程已經非常複雜,涉及的知識點也很多,簡單的說我這次的待機過程,就是通過SMC(secure monitor call),從EL1(kernel) -> EL3(secure monitor)。玩過在armv8-a板子上跑Linux系統的同學應該都知道,現在燒錄的uboot會包括BL1、BL2、BL31、BL32和BL33幾個階段,我們可能會比較熟悉BL33這個階段,因爲就是在這個階段引導Linux系統的。而從上面鏈接的內容可知,我所說的待機就是通過smc指令,觸發在bl1中設定的smc異常來將cpu權限交給bl31並跳轉到bl31中執行。
回到我們的問題,0xC4000001這個值對應的函數就是在BL31階段,所以我們需要進入到這個階段的代碼中看。但是很可惜,我這個平臺並沒有提供除BL33之外的其它BLx階段的源碼。我在網上找了一個其它平臺的代碼,雖然不能完全對應的上,但是也能知道個大概。
/*******************************************************************************
* PSCI top level handler for servicing SMCs.
******************************************************************************/
uint64_t psci_smc_handler(uint32_t smc_fid,
uint64_t x1,
uint64_t x2,
uint64_t x3,
uint64_t x4,
void *cookie,
void *handle,
uint64_t flags)
{
if (is_caller_secure(flags))
SMC_RET1(handle, SMC_UNK);
/* Check the fid against the capabilities */
if (!(psci_caps & define_psci_cap(smc_fid)))
SMC_RET1(handle, SMC_UNK);
if (((smc_fid >> FUNCID_CC_SHIFT) & FUNCID_CC_MASK) == SMC_32) {
/* 32-bit PSCI function, clear top parameter bits */
...
} else {
/* 64-bit PSCI function */
switch (smc_fid) {
case PSCI_CPU_SUSPEND_AARCH64: /* 對應我們的CPU_SUSPEND */
SMC_RET1(handle, psci_cpu_suspend(x1, x2, x3));
...
}
}
WARN("Unimplemented PSCI Call: 0x%x \n", smc_fid);
SMC_RET1(handle, SMC_UNK);
}
接着調用
int psci_cpu_suspend(unsigned int power_state,
unsigned long entrypoint,
unsigned long context_id)
{
...
/* Check SBZ bits in power state are zero */
if (psci_validate_power_state(power_state))
return PSCI_E_INVALID_PARAMS;
...
}
其中,
#define psci_validate_power_state(pstate) (pstate & PSTATE_VALID_MASK)
#define PSTATE_VALID_MASK 0xFCFE0000
所以我們發現,因爲 0x0020000 & 0xFCFE0000 = 0x0020000,所以返回了PSCI_E_INVALID_PARAMS,也就對應上我一開始提到的問題,返回了PSCI_RET_INVALID_PARAMS錯誤碼。
那爲什麼要和0xFCFE0000這個值進行與操作,從PSCI規範我們可以看到,對於32bit的power state參數,它對每一位都是做了嚴格要求的:
bit filed description
31:26 reserved.must be zero
25:24 power level
23:17 reserved.must be zero
16 state type
15:0 state ID
因爲我們的arm,psci-suspend-param值佔用了保留位,所以這是不合法的。
結語
最後的分析原因,可能不一定就是從那個位置返回,不過我們旨在是瞭解整個流程,到這裏我們就結束了這系列的文章。