feat: skip asymmetric tests when features not present

This patch skips asymmetric tests, when features are not present
and will split them into separate tests.

The problem with the previous test structure was that you can't
distinguish between a trap to EL2 and an undef injection. This meant
that on some platforms, the tests would pass even without the
asymmetric support patches. In these cases, it would be better if the
test was skipped, since there's no situation where it fails.

For example, if FEAT_SPE wasn't present on any cores, and the
asymmetric support patches weren't applied, then the test would pass.
This is because the register accesses would trap to EL2.

This patch skips the test on every core that doesn't have the feature
implemented. It also splits the test into separate test functions.
This allows us to display a separate test result for each asymmetric
test. It also allows us to skip the whole test if the feature isn't
present on any cores, since in these cases the test would always pass.

The structure of the test is similar to
tftf/tests/runtime_services/standard_service/psci/api_tests/cpu_suspend/test_suspend.c.
The run_asymmetric_test function takes a function as an argument, and
runs it on all CPUs.

The whole test should only be skipped if the test was skipped on all
CPUs. The test on each CPU can't return TEST_RESULT_SKIPPED, because
the whole test is skipped if any of the CPUs return
TEST_RESULT_SKIPPED. Instead, to skip a test, the test returns
TEST_RESULT_SUCCESS, then sets a flag in the test_skipped array. This
array is checked at the end by the run_asymmetric_test function.

Change-Id: I802431714de3eb8b059e8fc56f7e19fc94e3e8fb
Signed-off-by: Charlie Bareham <charlie.bareham@arm.com>
Signed-off-by: Jayanth Dodderi Chidanand <jayanthdodderi.chidanand@arm.com>
diff --git a/include/lib/aarch64/arch.h b/include/lib/aarch64/arch.h
index d29f5a6..8081ff4 100644
--- a/include/lib/aarch64/arch.h
+++ b/include/lib/aarch64/arch.h
@@ -226,6 +226,7 @@
 #define ID_AA64DFR0_TRACEBUFFER_SHIFT		U(44)
 #define ID_AA64DFR0_TRACEBUFFER_MASK		ULL(0xf)
 #define ID_AA64DFR0_TRACEBUFFER_SUPPORTED	ULL(1)
+#define ID_AA64DFR0_TRACEBUFFER_WIDTH		U(4)
 
 /* ID_DFR0_EL1.Tracefilt definitions */
 #define ID_AA64DFR0_TRACEFILT_SHIFT		U(40)
diff --git a/include/lib/aarch64/arch_features.h b/include/lib/aarch64/arch_features.h
index f5dd294..5822dc4 100644
--- a/include/lib/aarch64/arch_features.h
+++ b/include/lib/aarch64/arch_features.h
@@ -450,4 +450,10 @@
 		ID_AA64ISAR1_LS64_MASK);
 }
 
+static inline bool is_feat_trbe_present(void)
+{
+	return EXTRACT(ID_AA64DFR0_TRACEBUFFER, read_id_aa64dfr0_el1())
+		>= ID_AA64DFR0_TRACEBUFFER_SUPPORTED;
+}
+
 #endif /* ARCH_FEATURES_H */
diff --git a/tftf/tests/misc_tests/test_asymmetric_features.c b/tftf/tests/misc_tests/test_asymmetric_features.c
index 322afc0..630cc69 100644
--- a/tftf/tests/misc_tests/test_asymmetric_features.c
+++ b/tftf/tests/misc_tests/test_asymmetric_features.c
@@ -19,20 +19,39 @@
 
 static event_t cpu_has_entered_test[PLATFORM_CORE_COUNT];
 
-static volatile bool exception_triggered;
+/* Used when catching synchronous exceptions. */
+static volatile bool exception_triggered[PLATFORM_CORE_COUNT];
 
-static unsigned int test_result;
+/*
+ * The whole test should only be skipped if the test was skipped on all CPUs.
+ * The test on each CPU can't return TEST_RESULT_SKIPPED, because the whole test
+ * is skipped if any of the CPUs return TEST_RESULT_SKIPPED. Instead, to skip a
+ * test, the test returns TEST_RESULT_SUCCESS, then sets a flag in the
+ * test_skipped array. This array is checked at the end by the
+ * run_asymmetric_test function.
+ */
+static volatile bool test_skipped[PLATFORM_CORE_COUNT];
+
+/*
+ * Test function which is run on each CPU. It is global so it is visible to all
+ * CPUS.
+ */
+static test_result_t (*asymmetric_test_function)(void);
 
 static bool exception_handler(void)
 {
+	unsigned int mpid = read_mpidr_el1() & MPID_MASK;
+	unsigned int core_pos = platform_get_core_pos(mpid);
+
 	uint64_t esr_el2 = read_esr_el2();
+
 	if (EC_BITS(esr_el2) == EC_UNKNOWN) {
 		/*
 		 * This may be an undef injection, or a trap to EL2 due to a
 		 * register not being present. Both cases have the same EC
 		 * value.
 		 */
-		exception_triggered = true;
+		exception_triggered[core_pos] = true;
 		return true;
 	}
 
@@ -43,26 +62,32 @@
 {
 	unsigned int mpid = read_mpidr_el1() & MPID_MASK;
 	unsigned int core_pos = platform_get_core_pos(mpid);
-	bool check_if_affected = is_trbe_errata_affected_core();
+	bool should_trigger_exception = is_trbe_errata_affected_core();
+
+	if (!is_feat_trbe_present()) {
+		test_skipped[core_pos] = true;
+		return TEST_RESULT_SUCCESS;
+	}
 
 	register_custom_sync_exception_handler(exception_handler);
-	exception_triggered = false;
+	exception_triggered[core_pos] = false;
 	read_trblimitr_el1();
 	unregister_custom_sync_exception_handler();
 
-	if (exception_triggered == true && check_if_affected == true) {
-		test_result = TEST_RESULT_SUCCESS;
+	/**
+	 * NOTE: TRBE as an asymmetric feature is as exceptional one.
+	 * Even if the hardware supports the feature, TF-A deliberately disables
+	 * it at EL3. In this scenario, when the register "TRBLIMITR_EL1" is
+	 * accessed, the registered undef injection handler should kick in and
+	 * the exception will be handled synchronously at EL2.
+	 */
+	if (exception_triggered[core_pos] != should_trigger_exception) {
 		tftf_testcase_printf("Exception triggered for core = %d "
 				     "when accessing TRB_LIMTR\n", core_pos);
-	} else if (exception_triggered == false && check_if_affected == false) {
-		test_result = TEST_RESULT_SUCCESS;
-		tftf_testcase_printf("TRB_LIMITR register accessible for core "
-				     "= %d\n", core_pos);
-	} else {
-		test_result = TEST_RESULT_FAIL;
+		return TEST_RESULT_FAIL;
 	}
 
-	return test_result;
+	return TEST_RESULT_SUCCESS;
 }
 
 static test_result_t test_spe(void)
@@ -70,56 +95,36 @@
 	unsigned int mpid = read_mpidr_el1() & MPID_MASK;
 	unsigned int core_pos = platform_get_core_pos(mpid);
 
-	register_custom_sync_exception_handler(exception_handler);
-	exception_triggered = false;
-	read_pmscr_el1();
-	unregister_custom_sync_exception_handler();
-
-	if (exception_triggered == true && !is_feat_spe_supported()) {
-		test_result = TEST_RESULT_SUCCESS;
-		tftf_testcase_printf("Exception triggered for core = %d "
-				     "when accessing PMSCR_EL1\n", core_pos);
-	} else if (exception_triggered == false &&
-		   is_feat_spe_supported()) {
-		test_result = TEST_RESULT_SUCCESS;
-		tftf_testcase_printf("PMSCR_EL1 register accessible for core = "
-				     "%d\n", core_pos);
-	} else {
-		test_result = TEST_RESULT_FAIL;
+	/**
+	 * NOTE: SPE as an asymmetric feature, we expect to access the
+	 * PMSCR_EL1 register, when supported in the hardware.
+	 * If the feature isn't supported, we skip the test.
+	 * So on each individual CPU, we verify whether the feature's presence
+	 * and based on it we access (if feature supported) or skip the test.
+	 */
+	if (!is_feat_spe_supported()) {
+		test_skipped[core_pos] = true;
+		return TEST_RESULT_SUCCESS;
 	}
 
-	return test_result;
+	read_pmscr_el1();
+
+	return TEST_RESULT_SUCCESS;
 }
 
 /*
- * Non-lead cpu function that checks if trblimitr_el1 is accessible,
- * on affected cores this causes a undef injection and passes.In cores that
- * are not affected test just passes. It fails in other cases.
+ * Runs on one CPU, and runs asymmetric_test_function.
  */
 static test_result_t non_lead_cpu_fn(void)
 {
 	unsigned int mpid = read_mpidr_el1() & MPID_MASK;
 	unsigned int core_pos = platform_get_core_pos(mpid);
-	test_result_t result;
-
-	test_result = TEST_RESULT_SUCCESS;
+	test_result_t test_result;
 
 	/* Signal to the lead CPU that the calling CPU has entered the test */
 	tftf_send_event(&cpu_has_entered_test[core_pos]);
 
-	result = test_trbe();
-	if (result != TEST_RESULT_SUCCESS) {
-		tftf_testcase_printf("test_trbe_enabled failed with result "
-				     "%d\n", result);
-		test_result = result;
-	}
-
-	result = test_spe();
-	if (result != TEST_RESULT_SUCCESS) {
-		tftf_testcase_printf("test_spe_support failed with result %d\n",
-				     result);
-		test_result = result;
-	}
+	test_result = asymmetric_test_function();
 
 	/* Ensure that EL3 still functional */
 	smc_args args;
@@ -135,39 +140,46 @@
 	return test_result;
 }
 
-/* This function kicks off non-lead cpus and the non-lead cpu function
- * checks if errata is applied or not using the test.
+/* Set some variables that are accessible to all CPUs. */
+void test_init(test_result_t (*test_function)(void))
+{
+	int i;
+
+	for (i = 0; i < PLATFORM_CORE_COUNT; i++) {
+		test_skipped[i] = false;
+		tftf_init_event(&cpu_has_entered_test[i]);
+	}
+
+	asymmetric_test_function = test_function;
+
+	/* Ensure the above writes are seen before any read */
+	dmbsy();
+}
+
+/*
+ * Run the given test function on all CPUs. If the test is skipped on all CPUs,
+ * the whole test is skipped. This is checked using the test_skipped array.
  */
-test_result_t test_asymmetric_features(void)
+test_result_t run_asymmetric_test(test_result_t (*test_function)(void))
 {
 	unsigned int lead_mpid;
 	unsigned int cpu_mpid, cpu_node;
 	unsigned int core_pos;
 	int psci_ret;
-
-	test_result_t result;
-
-	test_result = TEST_RESULT_SUCCESS;
+	bool all_cpus_skipped;
+	int i;
+	uint32_t aff_info;
+	test_result_t test_result;
 
 	lead_mpid = read_mpidr_el1() & MPID_MASK;
 
-	/* Testing TRBE and SPE feature in Lead core */
-	result = test_trbe();
-	if (result != TEST_RESULT_SUCCESS) {
-		tftf_testcase_printf("test_trbe_enabled failed with result "
-				     "%d\n", result);
-		test_result = result;
-	}
-
-	result = test_spe();
-	if (result != TEST_RESULT_SUCCESS) {
-		tftf_testcase_printf("test_spe_support failed with result %d\n",
-				     result);
-		test_result = result;
-	}
-
 	SKIP_TEST_IF_LESS_THAN_N_CPUS(2);
 
+	test_init(test_function);
+
+	/* run test on lead CPU */
+	test_result = test_function();
+
 	/* Power on all CPUs */
 	for_each_cpu(cpu_node) {
 		cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
@@ -193,11 +205,49 @@
 
 		core_pos = platform_get_core_pos(cpu_mpid);
 		tftf_wait_for_event(&cpu_has_entered_test[core_pos]);
-		if (test_result == TEST_RESULT_FAIL)
-			break;
 	}
 
-	unregister_custom_sync_exception_handler();
+	/* Wait for all non-lead CPUs to power down */
+	for_each_cpu(cpu_node) {
+		cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
+		/* Skip lead CPU */
+		if (cpu_mpid == lead_mpid)
+			continue;
 
-	return test_result;
+		do {
+			aff_info = tftf_psci_affinity_info(cpu_mpid,
+							   MPIDR_AFFLVL0);
+		} while (aff_info != PSCI_STATE_OFF);
+	}
+
+	/*
+	 * If the test was skipped on all CPUs, the whole test should be
+	 * skipped.
+	 */
+
+	all_cpus_skipped = true;
+	for (i = 0; i < PLATFORM_CORE_COUNT; i++) {
+		if (!test_skipped[i]) {
+			all_cpus_skipped = false;
+			break;
+		}
+	}
+
+	if (all_cpus_skipped) {
+		return TEST_RESULT_SKIPPED;
+	} else {
+		return test_result;
+	}
+}
+
+/* Test Asymmetric Support for FEAT_TRBE */
+test_result_t test_trbe_errata_asymmetric(void)
+{
+	return run_asymmetric_test(test_trbe);
+}
+
+/* Test Asymmetric Support for FEAT_SPE */
+test_result_t test_spe_asymmetric(void)
+{
+	return run_asymmetric_test(test_spe);
 }
diff --git a/tftf/tests/tests-asymmetric-features.xml b/tftf/tests/tests-asymmetric-features.xml
index 413ef2c..29dfd7a 100644
--- a/tftf/tests/tests-asymmetric-features.xml
+++ b/tftf/tests/tests-asymmetric-features.xml
@@ -8,7 +8,9 @@
 
 <testsuites>
   <testsuite name="Asymmetric Features" description="Asymmetric features support">
-      <testcase name="Asymmetric Features"
-                function="test_asymmetric_features" />
+      <testcase name="TRBE Errata Asymmetric Test"
+                function="test_trbe_errata_asymmetric" />
+      <testcase name="Asymmetric SPE Test"
+                function="test_spe_asymmetric" />
   </testsuite>
 </testsuites>