最近遇到一个待机问题,系统一直无法正常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值占用了保留位,所以这是不合法的。
最后的分析原因,可能不一定就是从那个位置返回,不过我们旨在是了解整个流程,到这里我们就结束了这系列的文章。