test(psci): add test to validate "psci_is_last_cpu_to_idle_at_pwrlvl"
* This patch adds a test that suspends to affinity level 2 with another
CPU running in a different power domain.
* Previously with the bug identified and resolved in commit (01959a1),
the function "psci_is_last_cpu_to_idle_at_pwrlvl" checked only one
power domain when suspending to level2. This meant that if there was
a cpu running outside the power domain of the calling CPU, the suspend
request would be allowed. But in this case, the request should be
denied. This test case validates this behaviour and ensures the
request is denied.
* This patch also adds the following global variables to parameterise
and reuse the existing functions for the new test.
* test_should_suspend - an boolean array that allows you to leave
some CPUs running.
* test_should_deny - a boolean to specify if the suspend request
should be denied.
* cpu_finished - an array of events so that the running CPUs know
when to terminate.
* Additionally this patch also adds a function to get a CPU that is
in a different cluster to the lead CPU.
Refer to TF-A commit (01959a1) for more information on the bug.
Change-Id: Ib163aa8d5347baeaa47d1ae6f59599f1c68c11a8
Signed-off-by: Charlie Bareham <charlie.bareham@arm.com>
Signed-off-by: Jayanth Dodderi Chidanand <jayanthdodderi.chidanand@arm.com>
diff --git a/include/plat/common/plat_topology.h b/include/plat/common/plat_topology.h
index fbae878..ceb9eb5 100644
--- a/include/plat/common/plat_topology.h
+++ b/include/plat/common/plat_topology.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, Arm Limited. All rights reserved.
+ * Copyright (c) 2018-2025, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
@@ -181,6 +181,13 @@
unsigned int tftf_find_any_cpu_other_than(unsigned int exclude_mpid);
/*
+ * Query the platform topology to find another CPU that is in a different
+ * cluster to the one specified as an argument.
+ * Return the MPID of this other CPU, or INVALID_MPID if none could be found.
+ */
+unsigned int tftf_find_any_cpu_in_other_cluster(unsigned int exclude_mpid);
+
+/*
* Query the platform topology to find a random CPU other than the one specified
* as an argument.
* The difference between this function and tftf_find_any_cpu_other_than is
diff --git a/plat/common/plat_topology.c b/plat/common/plat_topology.c
index a9b9828..0ede8ff 100644
--- a/plat/common/plat_topology.c
+++ b/plat/common/plat_topology.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018-2020, Arm Limited. All rights reserved.
+ * Copyright (c) 2018-2025, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
@@ -365,6 +365,24 @@
return INVALID_MPID;
}
+unsigned int tftf_find_any_cpu_in_other_cluster(unsigned exclude_mpid)
+{
+ unsigned int cpu_node, cluster_node, mpidr, exclude_cluster_node;
+
+ exclude_cluster_node = tftf_get_parent_node_from_mpidr(exclude_mpid,
+ MPIDR_AFFLVL1);
+
+ for_each_cpu(cpu_node) {
+ mpidr = tftf_get_mpidr_from_node(cpu_node);
+ cluster_node = tftf_get_parent_node_from_mpidr(mpidr,
+ MPIDR_AFFLVL1);
+ if (cluster_node != exclude_cluster_node)
+ return mpidr;
+ }
+
+ return INVALID_MPID;
+}
+
unsigned int tftf_find_random_cpu_other_than(unsigned int exclude_mpid)
{
#if (PLATFORM_CORE_COUNT == 1)
diff --git a/tftf/tests/runtime_services/standard_service/psci/api_tests/cpu_suspend/test_suspend.c b/tftf/tests/runtime_services/standard_service/psci/api_tests/cpu_suspend/test_suspend.c
index 7da63ca..ba98df4 100644
--- a/tftf/tests/runtime_services/standard_service/psci/api_tests/cpu_suspend/test_suspend.c
+++ b/tftf/tests/runtime_services/standard_service/psci/api_tests/cpu_suspend/test_suspend.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, Arm Limited. All rights reserved.
+ * Copyright (c) 2018-2025, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
@@ -21,15 +21,18 @@
/*
* Desired affinity level, state type (standby or powerdown), and entry time for
- * each CPU in the next CPU_SUSPEND operation. We need these shared variables
- * because there is no way to pass arguments to non-lead CPUs...
+ * each CPU in the next CPU_SUSPEND operation. There are suspend flags so
+ * some CPUs can be left running. We need these shared variables because there
+ * is no way to pass arguments to non-lead CPUs.
*/
static unsigned int test_aff_level[PLATFORM_CORE_COUNT];
static unsigned int test_suspend_type[PLATFORM_CORE_COUNT];
static unsigned int test_suspend_entry_time[PLATFORM_CORE_COUNT];
+static bool test_should_suspend[PLATFORM_CORE_COUNT];
static event_t cpu_booted[PLATFORM_CORE_COUNT];
static event_t cpu_ready[PLATFORM_CORE_COUNT];
+static event_t cpu_finished[PLATFORM_CORE_COUNT];
/*
* Variable used by the non-lead CPUs to tell the lead CPU they
@@ -70,6 +73,7 @@
test_suspend_type[i] = suspend_type;
test_suspend_entry_time[i] =
PLAT_SUSPEND_ENTRY_TIME * PLATFORM_CORE_COUNT;
+ test_should_suspend[i] = true;
/*
* All testcases in this file use the same arrays so it needs to
@@ -77,6 +81,7 @@
*/
tftf_init_event(&cpu_booted[i]);
tftf_init_event(&cpu_ready[i]);
+ tftf_init_event(&cpu_finished[i]);
tftf_init_event(&event_received_wake_irq[i]);
requested_irq_received[i] = 0;
}
@@ -156,24 +161,45 @@
}
/*
- * CPU suspend test to the desired affinity level and power state
+ * Leave a non-load CPU running until the cpu_finished event is triggered.
+ */
+static test_result_t run_non_lead_cpu(void)
+{
+ unsigned int mpid = read_mpidr_el1();
+ unsigned int core_pos = platform_get_core_pos(mpid);
+
+ /* Signal to the lead CPU that the calling CPU has entered the test */
+ tftf_send_event(&cpu_booted[core_pos]);
+
+ /* Wait for signal from the lead CPU before suspending itself */
+ tftf_wait_for_event(&cpu_finished[core_pos]);
+
+ return TEST_RESULT_SUCCESS;
+}
+
+/*
+ * @brief: CPU suspend test to the desired affinity level and power state
+ * @param: Boolean flag to indicate when a suspend request should be denied.
*
+ * Test do:
* 1) Power on all cores
* 2) Each core registers a wake-up event to come out of suspend state
* 3) Each core tries to enter suspend state
*
* The test is skipped if an error occurs during the bring-up of non-lead CPUs.
+ * Some cores can be left running be setting the test_should_suspend array.
*/
-static test_result_t test_psci_suspend(void)
+static test_result_t test_psci_suspend(bool test_should_deny)
{
unsigned int lead_mpid = read_mpidr_el1() & MPID_MASK;
unsigned int target_mpid, target_node;
unsigned int core_pos;
unsigned int aff_level, suspend_type;
uint32_t power_state, stateid;
- int rc, expected_return_val;
+ int rc, composite_state_rc, expected_return_val;
int aff_info;
u_register_t flags;
+ test_result_t (*entry_point)(void);
/*
* Preparation step: Power on all cores.
@@ -184,9 +210,14 @@
if (target_mpid == lead_mpid)
continue;
- rc = tftf_cpu_on(target_mpid,
- (uintptr_t) suspend_non_lead_cpu,
- 0);
+ core_pos = platform_get_core_pos(target_mpid);
+ if (test_should_suspend[core_pos]) {
+ entry_point = suspend_non_lead_cpu;
+ } else {
+ entry_point = run_non_lead_cpu;
+ }
+
+ rc = tftf_cpu_on(target_mpid, (uintptr_t) entry_point, 0);
if (rc != PSCI_E_SUCCESS) {
tftf_testcase_printf(
"Failed to power on CPU 0x%x (%d)\n",
@@ -214,8 +245,10 @@
continue;
core_pos = platform_get_core_pos(target_mpid);
- tftf_send_event(&cpu_ready[core_pos]);
- waitms(PLAT_SUSPEND_ENTRY_TIME);
+ if (test_should_suspend[core_pos]) {
+ tftf_send_event(&cpu_ready[core_pos]);
+ waitms(PLAT_SUSPEND_ENTRY_TIME);
+ }
}
/* IRQs need to be disabled prior to programming the timer */
@@ -239,7 +272,7 @@
core_pos = platform_get_core_pos(lead_mpid);
aff_level = test_aff_level[core_pos];
suspend_type = test_suspend_type[core_pos];
- expected_return_val = tftf_psci_make_composite_state_id(aff_level,
+ composite_state_rc = tftf_psci_make_composite_state_id(aff_level,
suspend_type,
&stateid);
@@ -274,7 +307,24 @@
continue;
core_pos = platform_get_core_pos(target_mpid);
- tftf_wait_for_event(&event_received_wake_irq[core_pos]);
+ if (test_should_suspend[core_pos]) {
+ tftf_wait_for_event(&event_received_wake_irq[core_pos]);
+ }
+ }
+
+ if (test_should_deny) {
+ /*
+ * Signal to all non-lead CPUs that the test has finished.
+ */
+ for_each_cpu(target_node) {
+ target_mpid = tftf_get_mpidr_from_node(target_node);
+ /* Skip lead CPU */
+ if (target_mpid == lead_mpid)
+ continue;
+
+ core_pos = platform_get_core_pos(target_mpid);
+ tftf_send_event(&cpu_finished[core_pos]);
+ }
}
/* Wait for all non-lead CPUs to power down */
@@ -290,6 +340,7 @@
} while (aff_info != PSCI_STATE_OFF);
}
+ expected_return_val = test_should_deny ? PSCI_E_DENIED : composite_state_rc;
if (rc == expected_return_val)
return TEST_RESULT_SUCCESS;
@@ -303,7 +354,8 @@
* affinity level
*/
static test_result_t test_psci_suspend_level(unsigned int aff_level,
- unsigned int suspend_type)
+ unsigned int suspend_type,
+ bool should_deny)
{
int rc;
@@ -311,7 +363,7 @@
if (rc != TEST_RESULT_SUCCESS)
return rc;
- return test_psci_suspend();
+ return test_psci_suspend(should_deny);
}
/*
@@ -319,7 +371,9 @@
*/
test_result_t test_psci_suspend_powerdown_level0(void)
{
- return test_psci_suspend_level(PSTATE_AFF_LVL_0, PSTATE_TYPE_POWERDOWN);
+ return test_psci_suspend_level(PSTATE_AFF_LVL_0,
+ PSTATE_TYPE_POWERDOWN,
+ false);
}
/*
@@ -327,7 +381,9 @@
*/
test_result_t test_psci_suspend_standby_level0(void)
{
- return test_psci_suspend_level(PSTATE_AFF_LVL_0, PSTATE_TYPE_STANDBY);
+ return test_psci_suspend_level(PSTATE_AFF_LVL_0,
+ PSTATE_TYPE_STANDBY,
+ false);
}
/*
@@ -335,7 +391,9 @@
*/
test_result_t test_psci_suspend_powerdown_level1(void)
{
- return test_psci_suspend_level(PSTATE_AFF_LVL_1, PSTATE_TYPE_POWERDOWN);
+ return test_psci_suspend_level(PSTATE_AFF_LVL_1,
+ PSTATE_TYPE_POWERDOWN,
+ false);
}
/*
@@ -343,7 +401,9 @@
*/
test_result_t test_psci_suspend_standby_level1(void)
{
- return test_psci_suspend_level(PSTATE_AFF_LVL_1, PSTATE_TYPE_STANDBY);
+ return test_psci_suspend_level(PSTATE_AFF_LVL_1,
+ PSTATE_TYPE_STANDBY,
+ false);
}
/*
@@ -351,7 +411,9 @@
*/
test_result_t test_psci_suspend_powerdown_level2(void)
{
- return test_psci_suspend_level(PSTATE_AFF_LVL_2, PSTATE_TYPE_POWERDOWN);
+ return test_psci_suspend_level(PSTATE_AFF_LVL_2,
+ PSTATE_TYPE_POWERDOWN,
+ false);
}
/*
@@ -359,7 +421,9 @@
*/
test_result_t test_psci_suspend_standby_level2(void)
{
- return test_psci_suspend_level(PSTATE_AFF_LVL_2, PSTATE_TYPE_STANDBY);
+ return test_psci_suspend_level(PSTATE_AFF_LVL_2,
+ PSTATE_TYPE_STANDBY,
+ false);
}
/*
@@ -367,7 +431,9 @@
*/
test_result_t test_psci_suspend_powerdown_level3(void)
{
- return test_psci_suspend_level(PSTATE_AFF_LVL_3, PSTATE_TYPE_POWERDOWN);
+ return test_psci_suspend_level(PSTATE_AFF_LVL_3,
+ PSTATE_TYPE_POWERDOWN,
+ false);
}
/*
@@ -375,7 +441,9 @@
*/
test_result_t test_psci_suspend_standby_level3(void)
{
- return test_psci_suspend_level(PSTATE_AFF_LVL_3, PSTATE_TYPE_STANDBY);
+ return test_psci_suspend_level(PSTATE_AFF_LVL_3,
+ PSTATE_TYPE_STANDBY,
+ false);
}
/*
@@ -390,7 +458,7 @@
if (err != PSCI_E_SUCCESS)
return TEST_RESULT_FAIL;
- rc = test_psci_suspend_level(PSTATE_AFF_LVL_0, suspend_type);
+ rc = test_psci_suspend_level(PSTATE_AFF_LVL_0, suspend_type, false);
err = tftf_psci_set_suspend_mode(PSCI_PLAT_COORD);
if (err != PSCI_E_SUCCESS)
@@ -459,7 +527,7 @@
}
}
- rc = test_psci_suspend();
+ rc = test_psci_suspend(false);
err = tftf_psci_set_suspend_mode(PSCI_PLAT_COORD);
if (err != PSCI_E_SUCCESS)
@@ -490,7 +558,8 @@
* @Test_Aim@ Suspend to the specified suspend type targeted at affinity level 2
* in OS-initiated mode
*/
-static test_result_t test_psci_suspend_level2_osi(unsigned int suspend_type)
+static test_result_t test_psci_suspend_level2_osi(unsigned int suspend_type,
+ bool should_deny)
{
unsigned int lead_mpid = read_mpidr_el1() & MPID_MASK;
unsigned int lead_lvl_1_node =
@@ -552,7 +621,63 @@
}
- rc = test_psci_suspend();
+ rc = test_psci_suspend(should_deny);
+
+ err = tftf_psci_set_suspend_mode(PSCI_PLAT_COORD);
+ if (err != PSCI_E_SUCCESS)
+ return TEST_RESULT_FAIL;
+
+ return rc;
+}
+
+/*
+ * @Test_Aim@ Suspend to powerdown state targeted at affinity level 2, in
+ * OS-Initiated mode, with two CPUs left running, so the suspend call should be
+ * denied.
+ *
+ * This test was added to catch a specific bug. The bug made it so that the
+ * function only checked one power domain when suspending to affinity level 2.
+ * This meant that if there was a cpu running outside the power domain of the
+ * calling CPU, the suspend request would be allowed. But in this case, the
+ * request should be denied.
+ */
+test_result_t test_psci_suspend_invalid(void)
+{
+ unsigned int lead_mpid = read_mpidr_el1() & MPID_MASK;
+ unsigned int lead_core_pos = platform_get_core_pos(lead_mpid);
+ unsigned int running_mpid, running_core_pos;
+ int32_t err;
+ test_result_t rc;
+
+ /*
+ * This test requires at least two clusters.
+ */
+ if (tftf_get_total_aff_count(MPIDR_AFFLVL1) < 2) {
+ return TEST_RESULT_SKIPPED;
+ }
+
+ /*
+ * Non-lead CPUs should be suspended, and the lead CPU should
+ * attempt to supend to level 2. As there is a cpu running in another
+ * cluster, in this case the request from the lead CPU will be denied.
+ */
+
+ rc = test_init(MPIDR_AFFLVL0, PSTATE_TYPE_POWERDOWN);
+ if (rc != TEST_RESULT_SUCCESS)
+ return rc;
+
+ test_aff_level[lead_core_pos] = MPIDR_AFFLVL2;
+
+ running_mpid = tftf_find_any_cpu_in_other_cluster(lead_mpid);
+ assert(running_mpid != INVALID_MPID);
+ running_core_pos = platform_get_core_pos(running_mpid);
+ test_should_suspend[running_core_pos] = false;
+
+ err = tftf_psci_set_suspend_mode(PSCI_OS_INIT);
+ if (err != PSCI_E_SUCCESS)
+ return TEST_RESULT_FAIL;
+
+ rc = test_psci_suspend(true);
err = tftf_psci_set_suspend_mode(PSCI_PLAT_COORD);
if (err != PSCI_E_SUCCESS)
@@ -567,7 +692,7 @@
*/
test_result_t test_psci_suspend_powerdown_level2_osi(void)
{
- return test_psci_suspend_level2_osi(PSTATE_TYPE_POWERDOWN);
+ return test_psci_suspend_level2_osi(PSTATE_TYPE_POWERDOWN, false);
}
/*
@@ -576,7 +701,7 @@
*/
test_result_t test_psci_suspend_standby_level2_osi(void)
{
- return test_psci_suspend_level2_osi(PSTATE_TYPE_STANDBY);
+ return test_psci_suspend_level2_osi(PSTATE_TYPE_STANDBY, false);
}
/*
@@ -663,7 +788,7 @@
}
}
- rc = test_psci_suspend();
+ rc = test_psci_suspend(false);
err = tftf_psci_set_suspend_mode(PSCI_PLAT_COORD);
if (err != PSCI_E_SUCCESS)
diff --git a/tftf/tests/tests-psci.xml b/tftf/tests/tests-psci.xml
index 918d9a7..c56b702 100644
--- a/tftf/tests/tests-psci.xml
+++ b/tftf/tests/tests-psci.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (c) 2018-2024, Arm Limited. All rights reserved.
+ Copyright (c) 2018-2025, Arm Limited. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
-->
@@ -47,7 +47,7 @@
<testcase name="CPU suspend to powerdown at level 1 in OSI mode" function="test_psci_suspend_powerdown_level1_osi" />
<testcase name="CPU suspend to powerdown at level 2 in OSI mode" function="test_psci_suspend_powerdown_level2_osi" />
<testcase name="CPU suspend to powerdown at level 3 in OSI mode" function="test_psci_suspend_powerdown_level3_osi" />
-
+ <testcase name="CPU suspend to powerdown at level 2 in OSI mode in an invalid state" function="test_psci_suspend_invalid" />
<testcase name="CPU suspend to standby at level 0 in OSI mode" function="test_psci_suspend_standby_level0_osi" />
<testcase name="CPU suspend to standby at level 1 in OSI mode" function="test_psci_suspend_standby_level1_osi" />
<testcase name="CPU suspend to standby at level 2 in OSI mode" function="test_psci_suspend_standby_level2_osi" />