aboutsummaryrefslogtreecommitdiff
path: root/tftf/tests/runtime_services/standard_service/psci/system_tests/test_psci_on_off_suspend_stress.c
diff options
context:
space:
mode:
Diffstat (limited to 'tftf/tests/runtime_services/standard_service/psci/system_tests/test_psci_on_off_suspend_stress.c')
-rw-r--r--tftf/tests/runtime_services/standard_service/psci/system_tests/test_psci_on_off_suspend_stress.c576
1 files changed, 576 insertions, 0 deletions
diff --git a/tftf/tests/runtime_services/standard_service/psci/system_tests/test_psci_on_off_suspend_stress.c b/tftf/tests/runtime_services/standard_service/psci/system_tests/test_psci_on_off_suspend_stress.c
new file mode 100644
index 000000000..99f685406
--- /dev/null
+++ b/tftf/tests/runtime_services/standard_service/psci/system_tests/test_psci_on_off_suspend_stress.c
@@ -0,0 +1,576 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch.h>
+#include <arch_helpers.h>
+#include <debug.h>
+#include <events.h>
+#include <irq.h>
+#include <plat_topology.h>
+#include <platform.h>
+#include <platform_def.h>
+#include <power_management.h>
+#include <psci.h>
+#include <sgi.h>
+#include <stdlib.h>
+#include <test_helpers.h>
+#include <tftf.h>
+#include <tftf_lib.h>
+
+#define STRESS_TEST_COUNT 100
+
+/* Per-CPU counters used for the coherency test */
+typedef struct cpu_pm_ops_desc {
+ spinlock_t lock;
+ unsigned int pcpu_count[PLATFORM_CORE_COUNT];
+} cpu_pm_ops_desc_t;
+
+static cpu_pm_ops_desc_t device_pm_ops_desc
+ __attribute__ ((section("tftf_coherent_mem")));
+static cpu_pm_ops_desc_t normal_pm_ops_desc;
+
+static event_t cpu_booted[PLATFORM_CORE_COUNT];
+static volatile unsigned int start_test;
+static unsigned int exit_test;
+static unsigned int power_state;
+/* The target for CPU ON requests */
+static volatile unsigned long long target_mpid;
+
+static spinlock_t counter_lock;
+static volatile unsigned int cpu_on_count;
+/* Whether CPU suspend calls should be thrown into the test */
+static unsigned int include_cpu_suspend;
+
+static test_result_t secondary_cpu_on_race_test(void);
+
+/*
+ * Utility function to wait for all CPUs other than the caller to be
+ * OFF.
+ */
+static void wait_for_non_lead_cpus(void)
+{
+ unsigned int lead_mpid = read_mpidr_el1() & MPID_MASK;
+ unsigned int target_mpid, target_node;
+
+ for_each_cpu(target_node) {
+ target_mpid = tftf_get_mpidr_from_node(target_node);
+ /* Skip lead CPU, as it is powered on */
+ if (target_mpid == lead_mpid)
+ continue;
+
+ while (tftf_psci_affinity_info(target_mpid, MPIDR_AFFLVL0)
+ != PSCI_STATE_OFF)
+ ;
+ }
+}
+
+/*
+ * Update per-cpu counter corresponding to the current CPU.
+ * This function updates 2 counters, one in normal memory and the other
+ * in coherent device memory. The counts are then compared to check if they
+ * match. This verifies that the caches and the interconnect are coherent
+ * during the test.
+ * Returns -1 on error, 0 on success.
+ */
+static int update_counters(void)
+{
+ unsigned int normal_count, device_count;
+ unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
+
+ /*
+ * Ensure that the copies of the counters in device and normal memory
+ * match. The locks and the data should become incoherent if any cluster
+ * is not taking part in coherency.
+ */
+ spin_lock(&normal_pm_ops_desc.lock);
+ normal_count = normal_pm_ops_desc.pcpu_count[core_pos];
+ spin_unlock(&normal_pm_ops_desc.lock);
+
+ spin_lock(&device_pm_ops_desc.lock);
+ device_count = device_pm_ops_desc.pcpu_count[core_pos];
+ spin_unlock(&device_pm_ops_desc.lock);
+
+ if (device_count != normal_count) {
+ tftf_testcase_printf("Count mismatch. Device memory count ="
+ " %u: normal memory count = %u\n",
+ device_count, normal_count);
+ return -1;
+ }
+
+ /* Increment the count in both copies of the counter */
+ spin_lock(&normal_pm_ops_desc.lock);
+ normal_pm_ops_desc.pcpu_count[core_pos]++;
+ spin_unlock(&normal_pm_ops_desc.lock);
+
+ spin_lock(&device_pm_ops_desc.lock);
+ device_pm_ops_desc.pcpu_count[core_pos]++;
+ spin_unlock(&device_pm_ops_desc.lock);
+
+ return 0;
+}
+
+/*
+ * The test loop for non lead CPUs in psci_on_off_suspend_coherency_test. It
+ * updates the counters and depending on the value of the random variable `op`,
+ * the secondaries either offlines (by returning back to Test framework) or
+ * suspends itself.
+ */
+static test_result_t random_suspend_off_loop(void)
+{
+#define OFFLINE_CORE 1
+#define SUSPEND_CORE 0
+
+ int rc, op;
+
+ while (!exit_test) {
+ rc = update_counters();
+ if (rc)
+ return TEST_RESULT_FAIL;
+
+ /* Find what we will be doing next */
+ op = rand() % 2;
+
+ /*
+ * If the chosen action is to power off, then return from the
+ * test function so that the test framework powers this CPU off.
+ */
+ if (op == OFFLINE_CORE)
+ return TEST_RESULT_SUCCESS;
+
+ /* Program timer for wake-up event. */
+ rc = tftf_program_timer_and_suspend(PLAT_SUSPEND_ENTRY_TIME,
+ power_state, NULL, NULL);
+
+ tftf_cancel_timer();
+
+ if (rc != PSCI_E_SUCCESS) {
+ tftf_testcase_printf("CPU timer/suspend returned error"
+ " 0x%x\n", rc);
+ return TEST_RESULT_FAIL;
+ }
+ }
+ return TEST_RESULT_SUCCESS;
+}
+
+static test_result_t lead_cpu_main(unsigned long long mpid)
+{
+ unsigned int rc;
+ unsigned long long rand_mpid;
+ int i;
+
+ /* The lead cpu will not be turned off. */
+ for (i = STRESS_TEST_COUNT; i >= 0; i--) {
+ rc = update_counters();
+ if (rc)
+ return TEST_RESULT_FAIL;
+
+ /* Program timer for wake-up event. */
+ rc = tftf_program_timer_and_suspend(PLAT_SUSPEND_ENTRY_TIME,
+ power_state, NULL, NULL);
+
+ tftf_cancel_timer();
+
+ if (rc != PSCI_E_SUCCESS) {
+ tftf_testcase_printf("CPU timer/suspend returned error"
+ " 0x%x\n", rc);
+ return TEST_RESULT_FAIL;
+ }
+
+ /*
+ * The lead cpu's woken up since the system timer has fired.
+ * For any cpus which have turned themselves off, generate a
+ * random MPIDR and try turning on the corresponding cpu.
+ */
+ do {
+ rand_mpid = tftf_find_random_cpu_other_than(mpid);
+ } while (tftf_psci_affinity_info(rand_mpid, MPIDR_AFFLVL0)
+ != PSCI_STATE_OFF);
+
+ rc = tftf_try_cpu_on(rand_mpid,
+ (uintptr_t) random_suspend_off_loop,
+ 0);
+ if ((rc != PSCI_E_ALREADY_ON) &&
+ (rc != PSCI_E_ON_PENDING) &&
+ (rc != PSCI_E_SUCCESS) &&
+ (rc != PSCI_E_INVALID_PARAMS)) {
+ tftf_testcase_printf("CPU ON failed with error ="
+ " 0x%x\n", rc);
+ return TEST_RESULT_FAIL;
+ }
+ }
+
+ exit_test = 1;
+ /* Ensure update to `exit_test` is seen by all cores prior to
+ invoking wait_for_non_lead_cpus() */
+ dmbsy();
+
+ wait_for_non_lead_cpus();
+
+ INFO("Exiting test\n");
+ return TEST_RESULT_SUCCESS;
+}
+
+/*
+ * Send event depicting CPU booted successfully and then invoke
+ * random_suspend_off_loop.
+ */
+static test_result_t non_lead_random_suspend_off_loop(void)
+{
+ unsigned long long mpid = read_mpidr_el1() & MPID_MASK;
+ unsigned int core_pos = platform_get_core_pos(mpid);
+
+ /* Tell the lead CPU that the calling CPU has entered the test */
+ tftf_send_event(&cpu_booted[core_pos]);
+
+ return random_suspend_off_loop();
+}
+
+/*
+ * @Test_Aim@ Repeated cores hotplug as stress test
+ * Test Do :
+ * 1) Power up all the cores
+ * 2) Ensure all the cores have booted successfully to TFTF
+ * 3) Randomly suspend or turn OFF secondary CPU
+ * 4) The lead CPU will suspend and turn ON a random CPU which has powered OFF.
+ * 5) Repeat 1-4 STRESS_TEST_COUNT times
+ * 6) The test is aborted straight away if any failure occurs. In this case,
+ * the test is declared as failed.
+ * Note: The test will be skipped on single-core platforms.
+ */
+test_result_t psci_on_off_suspend_coherency_test(void)
+{
+ unsigned int cpu_node, core_pos;
+ unsigned long long cpu_mpid, lead_mpid = read_mpidr_el1() & MPID_MASK;
+ unsigned int counter_lo, stateid;
+ int psci_ret;
+
+ SKIP_TEST_IF_LESS_THAN_N_CPUS(2);
+
+ /* Reinitialize the event variable */
+ for (unsigned int j = 0; j < PLATFORM_CORE_COUNT; ++j)
+ tftf_init_event(&cpu_booted[j]);
+
+ init_spinlock(&normal_pm_ops_desc.lock);
+ init_spinlock(&device_pm_ops_desc.lock);
+
+ exit_test = 0;
+
+ /* Seed the random number generator */
+ counter_lo = (unsigned int) read_cntpct_el0();
+ srand(counter_lo);
+
+ psci_ret = tftf_psci_make_composite_state_id(PLAT_MAX_PWR_LEVEL,
+ PSTATE_TYPE_POWERDOWN, &stateid);
+ if (psci_ret != PSCI_E_SUCCESS) {
+ tftf_testcase_printf("Failed to construct composite state\n");
+ return TEST_RESULT_SKIPPED;
+ }
+ power_state = tftf_make_psci_pstate(MPIDR_AFFLVL0,
+ PSTATE_TYPE_POWERDOWN, stateid);
+
+ /* Turn on all the non-lead CPUs */
+ for_each_cpu(cpu_node) {
+ cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
+ /* Skip lead CPU, it is already powered on */
+ if (cpu_mpid == lead_mpid)
+ continue;
+
+ psci_ret = tftf_cpu_on(cpu_mpid,
+ (uintptr_t) non_lead_random_suspend_off_loop, 0);
+ if (psci_ret != PSCI_E_SUCCESS)
+ return TEST_RESULT_FAIL;
+ }
+
+ /*
+ * Confirm the non-lead cpus booted and participated in the test
+ */
+ for_each_cpu(cpu_node) {
+ cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
+ /* Skip lead CPU, it is already powered on */
+ if (cpu_mpid == lead_mpid)
+ continue;
+
+ core_pos = platform_get_core_pos(cpu_mpid);
+ tftf_wait_for_event(&cpu_booted[core_pos]);
+ }
+
+ core_pos = platform_get_core_pos(lead_mpid);
+ return lead_cpu_main(lead_mpid);
+}
+
+/*
+ * Frantically send CPU ON requests to the target mpidr till it returns
+ * ALREADY_ON.
+ * Return 0 on success, 1 on error.
+ */
+static int test_cpu_on_race(void)
+{
+ int ret;
+
+ do {
+ ret = tftf_try_cpu_on(target_mpid,
+ (uintptr_t) secondary_cpu_on_race_test, 0);
+ if (ret != PSCI_E_SUCCESS && ret != PSCI_E_ON_PENDING &&
+ ret != PSCI_E_ALREADY_ON) {
+ tftf_testcase_printf("Unexpected return value 0x%x"
+ " from PSCI CPU ON\n", ret);
+ return 1;
+ }
+ } while (ret != PSCI_E_ALREADY_ON);
+
+ return 0;
+}
+
+/*
+ * This function runs the test_cpu_on_race() till either `exit_test`
+ * is set or the `target_mpid` is the current mpid.
+ */
+static test_result_t secondary_cpu_on_race_test(void)
+{
+ unsigned long long mpid = read_mpidr_el1() & MPID_MASK;
+ unsigned int core_pos = platform_get_core_pos(mpid);
+ int ret;
+
+ /* Tell the lead CPU that the calling CPU has entered the test */
+ tftf_send_event(&cpu_booted[core_pos]);
+
+ /* Wait for start flag */
+ while (start_test == 0)
+ ;
+
+ do {
+ /*
+ * If the current CPU is the target mpid, then power OFF.
+ * The target mpid will be target for CPU ON requests by other
+ * cores.
+ */
+ if (mpid == target_mpid)
+ return TEST_RESULT_SUCCESS;
+
+ ret = test_cpu_on_race();
+ if (ret)
+ return TEST_RESULT_FAIL;
+ } while (exit_test == 0);
+
+ return TEST_RESULT_SUCCESS;
+}
+
+/*
+ * @Test_Aim@ Verify that the CPU ON race conditions are handled in Firmware.
+ * Test Do :
+ * 1. Designate a target CPU to power down.
+ * 2. Boot up all the CPUs on the system
+ * 3. If the CPU is the designated target CPU, power OFF
+ * 4. All the other cores issue CPU ON to the target CPU continuously.
+ * As per PSCI specification, only one CPU ON call will succeed in sending
+ * the ON command to the target CPU.
+ * 5. The Target CPU should turn ON and execute successfully.
+ * 6. The test should iterate again with another CPU as the target CPU this
+ * time.
+ * 7. Repeat Steps 1-6 for STRESS_TEST_COUNT times.
+ * Note: The test will be skipped on single-core platforms.
+ */
+test_result_t psci_verify_cpu_on_race(void)
+{
+ unsigned int cpu_node, core_pos, target_node, j;
+ unsigned long long lead_mpid = read_mpidr_el1() & MPID_MASK, cpu_mpid;
+ int ret, rc = 0;
+
+ exit_test = 0;
+ start_test = 0;
+
+ SKIP_TEST_IF_LESS_THAN_N_CPUS(2);
+
+ /* Reinitialize the event variable */
+ for (j = 0; j < PLATFORM_CORE_COUNT; j++)
+ tftf_init_event(&cpu_booted[j]);
+
+ /* Turn ON all other CPUs */
+ for_each_cpu(cpu_node) {
+ cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
+
+ /* Skip the lead CPU */
+ if (cpu_mpid == lead_mpid)
+ continue;
+
+ ret = tftf_cpu_on(cpu_mpid,
+ (uintptr_t) secondary_cpu_on_race_test, 0);
+ if (ret != PSCI_E_SUCCESS)
+ return TEST_RESULT_FAIL;
+
+ core_pos = platform_get_core_pos(cpu_mpid);
+ tftf_wait_for_event(&cpu_booted[core_pos]);
+ }
+
+ for (j = 0; j < STRESS_TEST_COUNT; j++) {
+ /* Choose a target CPU */
+ for_each_cpu(target_node) {
+ cpu_mpid = tftf_get_mpidr_from_node(target_node);
+
+ /* Skip the lead CPU */
+ if (cpu_mpid == lead_mpid)
+ continue;
+
+ target_mpid = cpu_mpid;
+ /*
+ * Ensure target_mpid update is visible prior to
+ * starting test.
+ */
+ dmbsy();
+
+ VERBOSE("Target MPID = %llx\n", target_mpid);
+ start_test = 1;
+
+ /* Wait for the target CPU to turn OFF */
+ while (tftf_psci_affinity_info(target_mpid,
+ MPIDR_AFFLVL0) != PSCI_STATE_OFF)
+ ;
+ rc = test_cpu_on_race();
+ if (rc)
+ break;
+ }
+ if (rc)
+ break;
+ }
+ exit_test = 1;
+ wait_for_non_lead_cpus();
+ return rc ? TEST_RESULT_FAIL : TEST_RESULT_SUCCESS;
+}
+
+/*
+ * The Test function to stress test CPU ON/OFF PSCI APIs executed by all CPUs.
+ * This function maintains a global counter which is incremented atomically
+ * when a CPU enters this function via warm boot (in case of secondary) or
+ * direct invocation (in case of lead CPU). It sends CPU ON to all OFF CPUs
+ * till another CPU has entered this function or `exit_flag` is set.
+ * If `include_cpu_suspend` is set, then it suspends the current CPU
+ * before iterating in the loop to send CPU ON requests.
+ */
+static test_result_t launch_cpu_on_off_stress(void)
+{
+ unsigned long long mpid = read_mpidr_el1() & MPID_MASK;
+ unsigned int cpu_node, temp_count;
+ int ret;
+
+ spin_lock(&counter_lock);
+ /* Store the count in a temporary variable */
+ temp_count = ++cpu_on_count;
+ spin_unlock(&counter_lock);
+
+ if (exit_test)
+ return TEST_RESULT_SUCCESS;
+
+ while (!exit_test) {
+ for_each_cpu(cpu_node) {
+ mpid = tftf_get_mpidr_from_node(cpu_node);
+
+ if (tftf_is_cpu_online(mpid))
+ continue;
+
+ ret = tftf_try_cpu_on(mpid,
+ (uintptr_t) launch_cpu_on_off_stress, 0);
+ if (ret != PSCI_E_SUCCESS && ret !=
+ PSCI_E_ON_PENDING && ret != PSCI_E_ALREADY_ON) {
+ tftf_testcase_printf("Unexpected return value"
+ " 0x%x from PSCI CPU ON\n", ret);
+ return TEST_RESULT_FAIL;
+ }
+ }
+
+ /* Break if another CPU has entered this test function */
+ if (temp_count != cpu_on_count)
+ break;
+
+ /* Check whether to suspend before iterating */
+ if (include_cpu_suspend) {
+ ret = tftf_program_timer_and_suspend(
+ PLAT_SUSPEND_ENTRY_TIME, power_state, NULL, NULL);
+
+ tftf_cancel_timer();
+
+ if (ret != PSCI_E_SUCCESS) {
+ tftf_testcase_printf("CPU timer/suspend"
+ " returned error 0x%x\n", ret);
+ return TEST_RESULT_FAIL;
+ }
+ }
+ }
+
+ spin_lock(&counter_lock);
+ if (cpu_on_count >= (STRESS_TEST_COUNT * PLATFORM_CORE_COUNT)) {
+ cpu_on_count = 0;
+ spin_unlock(&counter_lock);
+ exit_test = 1;
+ /* Wait for all cores to power OFF */
+ wait_for_non_lead_cpus();
+
+ /*
+ * In case any other CPUs were turned ON in the meantime, wait
+ * for them as well.
+ */
+ wait_for_non_lead_cpus();
+ } else
+ spin_unlock(&counter_lock);
+
+ return TEST_RESULT_SUCCESS;
+}
+
+/*
+ * @Test_Aim@ Stress test CPU ON / OFF APIs.
+ * Test Do :
+ * 1. Maintain a global counter will be atomically updated by each CPU
+ * when it turns ON.
+ * 2. Iterate over all the CPU in the topology and invoke CPU ON on CPU
+ * which are OFF.
+ * 3. Check if the global counter has updated. If not, goto step 2
+ * 4. All the cores which are turned ON executes Step 1 - 3.
+ * Note: The test will be skipped on single-core platforms.
+ */
+test_result_t psci_cpu_on_off_stress(void)
+{
+ SKIP_TEST_IF_LESS_THAN_N_CPUS(2);
+ init_spinlock(&counter_lock);
+ cpu_on_count = 0;
+ exit_test = 0;
+ include_cpu_suspend = 0;
+ return launch_cpu_on_off_stress();
+}
+
+/*
+ * @Test_Aim@ Stress test CPU ON / OFF APIs with SUSPEND in between.
+ * Test Do :
+ * 1. Maintain a global counter which will be atomically updated by each CPU
+ * when it turns ON.
+ * 2. Iterate over all the CPUs in the topology and invoke CPU ON on CPUs
+ * which are OFF.
+ * 3. Check if the global counter has updated. If not, program wakeup
+ * timer and suspend. On wake-up goto step 2.
+ * 4. All the cores which are turned ON executes Step 1 - 3.
+ * Note: The test will be skipped on single-core platforms.
+ */
+test_result_t psci_cpu_on_off_suspend_stress(void)
+{
+ int rc;
+ unsigned int stateid;
+
+ SKIP_TEST_IF_LESS_THAN_N_CPUS(2);
+
+ init_spinlock(&counter_lock);
+ cpu_on_count = 0;
+ exit_test = 0;
+
+ rc = tftf_psci_make_composite_state_id(PLAT_MAX_PWR_LEVEL,
+ PSTATE_TYPE_POWERDOWN, &stateid);
+ if (rc != PSCI_E_SUCCESS) {
+ tftf_testcase_printf("Failed to construct composite state\n");
+ return TEST_RESULT_SKIPPED;
+ }
+ power_state = tftf_make_psci_pstate(PLAT_MAX_PWR_LEVEL,
+ PSTATE_TYPE_POWERDOWN, stateid);
+
+ include_cpu_suspend = 1;
+ return launch_cpu_on_off_stress();
+}