Merge "test(psci): add test to validate "psci_is_last_cpu_to_idle_at_pwrlvl""
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" />