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" />