Trusted Firmware-A Tests, version 2.0

This is the first public version of the tests for the Trusted
Firmware-A project. Please see the documentation provided in the
source tree for more details.

Change-Id: I6f3452046a1351ac94a71b3525c30a4ca8db7867
Signed-off-by: Sandrine Bailleux <sandrine.bailleux@arm.com>
Co-authored-by: amobal01 <amol.balasokamble@arm.com>
Co-authored-by: Antonio Nino Diaz <antonio.ninodiaz@arm.com>
Co-authored-by: Asha R <asha.r@arm.com>
Co-authored-by: Chandni Cherukuri <chandni.cherukuri@arm.com>
Co-authored-by: David Cunado <david.cunado@arm.com>
Co-authored-by: Dimitris Papastamos <dimitris.papastamos@arm.com>
Co-authored-by: Douglas Raillard <douglas.raillard@arm.com>
Co-authored-by: dp-arm <dimitris.papastamos@arm.com>
Co-authored-by: Jeenu Viswambharan <jeenu.viswambharan@arm.com>
Co-authored-by: Jonathan Wright <jonathan.wright@arm.com>
Co-authored-by: Kévin Petit <kevin.petit@arm.com>
Co-authored-by: Roberto Vargas <roberto.vargas@arm.com>
Co-authored-by: Sathees Balya <sathees.balya@arm.com>
Co-authored-by: Shawon Roy <Shawon.Roy@arm.com>
Co-authored-by: Soby Mathew <soby.mathew@arm.com>
Co-authored-by: Thomas Abraham <thomas.abraham@arm.com>
Co-authored-by: Vikram Kanigiri <vikram.kanigiri@arm.com>
Co-authored-by: Yatharth Kochar <yatharth.kochar@arm.com>
diff --git a/tftf/framework/main.c b/tftf/framework/main.c
new file mode 100644
index 0000000..3f94dc9
--- /dev/null
+++ b/tftf/framework/main.c
@@ -0,0 +1,599 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch_helpers.h>
+#include <arm_gic.h>
+#include <assert.h>
+#include <debug.h>
+#include <irq.h>
+#include <mmio.h>
+#include <nvm.h>
+#include <plat_topology.h>
+#include <platform.h>
+#include <platform_def.h>
+#include <power_management.h>
+#include <psci.h>
+#include <sgi.h>
+#include <string.h>
+#include <sys/types.h>
+#include <tftf.h>
+#include <tftf_lib.h>
+#include <timer.h>
+
+/* version information for TFTF */
+extern const char version_string[];
+
+unsigned int lead_cpu_mpid;
+
+/* Defined in hotplug.c */
+extern volatile test_function_t test_entrypoint[PLATFORM_CORE_COUNT];
+
+/* Per-CPU results for the current test */
+static test_result_t test_results[PLATFORM_CORE_COUNT];
+
+/* Context ID passed to tftf_psci_cpu_on() */
+static u_register_t cpu_on_ctx_id_arr[PLATFORM_CORE_COUNT];
+
+static unsigned int test_is_rebooting;
+
+static inline const test_suite_t *current_testsuite(void)
+{
+	test_ref_t test_to_run;
+	tftf_get_test_to_run(&test_to_run);
+	return &testsuites[test_to_run.testsuite_idx];
+}
+
+static inline const test_case_t *current_testcase(void)
+{
+	test_ref_t test_to_run;
+	tftf_get_test_to_run(&test_to_run);
+	return &testsuites[test_to_run.testsuite_idx].
+		testcases[test_to_run.testcase_idx];
+}
+
+/*
+ * Identify the next test in the tests list and update the NVM data to point to
+ * that test.
+ * If there is no more tests to execute, return NULL.
+ * Otherwise, return the test case.
+ */
+static const test_case_t *advance_to_next_test(void)
+{
+	test_ref_t test_to_run;
+	const test_case_t *testcase;
+	unsigned int testcase_idx;
+	unsigned int testsuite_idx;
+
+#if DEBUG
+	test_progress_t progress;
+	tftf_get_test_progress(&progress);
+	assert(progress == TEST_COMPLETE);
+#endif
+
+	tftf_get_test_to_run(&test_to_run);
+	testcase_idx = test_to_run.testcase_idx;
+	testsuite_idx = test_to_run.testsuite_idx;
+
+	/* Move to the next test case in the current test suite */
+	++testcase_idx;
+	testcase = &testsuites[testsuite_idx].testcases[testcase_idx];
+
+	if (testcase->name == NULL) {
+		/*
+		 * There's no more test cases in the current test suite so move
+		 * to the first test case of the next test suite.
+		 */
+		const test_suite_t *testsuite;
+		testcase_idx = 0;
+		++testsuite_idx;
+		testsuite = &testsuites[testsuite_idx];
+		testcase = &testsuite->testcases[0];
+
+		if (testsuite->name == NULL) {
+			/*
+			 * This was the last test suite so there's no more tests
+			 * at all.
+			 */
+			return NULL;
+		}
+	}
+
+	VERBOSE("Moving to test (%u,%u)\n", testsuite_idx, testcase_idx);
+	test_to_run.testsuite_idx = testsuite_idx;
+	test_to_run.testcase_idx = testcase_idx;
+	tftf_set_test_to_run(test_to_run);
+	tftf_set_test_progress(TEST_READY);
+
+	return testcase;
+}
+
+/*
+ * This function is executed only by the lead CPU.
+ * It prepares the environment for the next test to run.
+ */
+static void prepare_next_test(void)
+{
+	unsigned int mpid;
+	unsigned int core_pos;
+	unsigned int cpu_node;
+
+	/* This function should be called by the lead CPU only */
+	assert((read_mpidr_el1() & MPID_MASK) == lead_cpu_mpid);
+
+	/*
+	 * Only the lead CPU should be powered on at this stage. All other CPUs
+	 * should be powered off or powering off. If some CPUs are not powered
+	 * off yet, wait for them to power off.
+	 */
+	for_each_cpu(cpu_node) {
+		mpid = tftf_get_mpidr_from_node(cpu_node);
+		if (mpid == lead_cpu_mpid)
+			assert(tftf_is_cpu_online(mpid));
+		else
+			while (tftf_psci_affinity_info(mpid, MPIDR_AFFLVL0)
+					  == PSCI_STATE_ON)
+				;
+	}
+
+	/* No CPU should have entered the test yet */
+	assert(tftf_get_ref_cnt() == 0);
+
+	/* Populate the test entrypoint for the lead CPU */
+	core_pos = platform_get_core_pos(lead_cpu_mpid);
+	test_entrypoint[core_pos] = (test_function_t) current_testcase()->test;
+
+	for (unsigned int i = 0; i < PLATFORM_CORE_COUNT; ++i)
+		test_results[i] = TEST_RESULT_NA;
+
+	NOTICE("Starting unittest '%s - %s'\n",
+		current_testsuite()->name, current_testcase()->name);
+
+	/* Program the watchdog */
+	tftf_platform_watchdog_set();
+
+	/* TODO: Take a 1st timestamp to be able to measure test duration */
+
+	tftf_set_test_progress(TEST_IN_PROGRESS);
+}
+
+/*
+ * Go through individual CPUs' test results and determine the overall
+ * test result from that.
+ */
+static test_result_t get_overall_test_result(void)
+{
+	test_result_t result = TEST_RESULT_NA;
+	unsigned int cpu_mpid;
+	unsigned int cpu_node;
+	unsigned int core_pos;
+
+	for_each_cpu(cpu_node) {
+		cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
+		core_pos = platform_get_core_pos(cpu_mpid);
+
+		switch (test_results[core_pos]) {
+		case TEST_RESULT_NA:
+			VERBOSE("CPU%u not involved in the test\n", core_pos);
+			/* Ignoring */
+			break;
+
+		case TEST_RESULT_SKIPPED:
+			/*
+			 * If at least one CPU skipped the test, consider the
+			 * whole test as skipped as well.
+			 */
+			NOTICE("CPU%u skipped the test\n", core_pos);
+			return TEST_RESULT_SKIPPED;
+
+		case TEST_RESULT_SUCCESS:
+			result = TEST_RESULT_SUCCESS;
+			break;
+
+		case TEST_RESULT_FAIL:
+			ERROR("CPU%u failed the test\n", core_pos);
+			return TEST_RESULT_FAIL;
+
+		case TEST_RESULT_CRASHED:
+			/*
+			 * Means the CPU never returned from the test whereas it
+			 * was supposed to. Either there is a bug in the test's
+			 * implementation or some sort of unexpected crash
+			 * happened.
+			 * If at least one CPU crashed, consider the whole test
+			 * as crashed as well.
+			 */
+			ERROR("CPU%u never returned from the test!\n", core_pos);
+			return TEST_RESULT_CRASHED;
+
+		default:
+			ERROR("Unknown test result value: %u\n",
+				test_results[core_pos]);
+			panic();
+		}
+	}
+
+	/*
+	 * At least one CPU (i.e. the lead CPU) should have participated in the
+	 * test.
+	 */
+	assert(result != TEST_RESULT_NA);
+	return result;
+}
+
+/*
+ * This function is executed by the last CPU to exit the test only.
+ * It does the necessary bookkeeping and reports the overall test result.
+ * If it was the last test, it will also generate the final test report.
+ * Otherwise, it will reset the platform, provided that the platform
+ * supports reset from non-trusted world. This ensures that the next test
+ * runs in a clean environment
+ *
+ * Return 1 if this was the last test, 0 otherwise.
+ */
+static unsigned int close_test(void)
+{
+	const test_case_t *next_test;
+
+#if DEBUG
+	/*
+	 * Check that the test didn't pretend resetting the platform, when in
+	 * fact it returned into the framework.
+	 *
+	 * If that happens, the test implementation should be fixed.
+	 * However, it is not a fatal error so just flag the problem in debug
+	 * builds.
+	 */
+	test_progress_t progress;
+	tftf_get_test_progress(&progress);
+	assert(progress != TEST_REBOOTING);
+#endif /* DEBUG */
+
+	tftf_set_test_progress(TEST_COMPLETE);
+	test_is_rebooting = 0;
+
+	/* TODO: Take a 2nd timestamp and compute test duration */
+
+	/* Reset watchdog */
+	tftf_platform_watchdog_reset();
+
+	/* Ensure no CPU is still executing the test */
+	assert(tftf_get_ref_cnt() == 0);
+
+	/* Save test result in NVM */
+	test_result_t overall_test_result = get_overall_test_result();
+	tftf_testcase_set_result(current_testcase(),
+				overall_test_result,
+				0);
+
+	NOTICE("Unittest '%s - %s' complete. Result: %s\n",
+	       current_testsuite()->name, current_testcase()->name,
+	       test_result_to_string(overall_test_result));
+
+	/* The test is finished, let's move to the next one (if any) */
+	next_test = advance_to_next_test();
+
+	/* If this was the last test then report all results */
+	if (!next_test) {
+		tftf_report_generate();
+		tftf_clean_nvm();
+		return 1;
+	} else {
+#if (PLAT_SUPPORTS_NS_RESET && !NEW_TEST_SESSION && USE_NVM)
+		/*
+		 * Reset the platform so that the next test runs in a clean
+		 * environment.
+		 */
+		INFO("Reset platform before executing next test:%p\n",
+				(void *) &(next_test->test));
+		tftf_plat_reset();
+		bug_unreachable();
+#endif
+	}
+
+	return 0;
+}
+
+/*
+ * Hand over to lead CPU, i.e.:
+ *  1) Power on lead CPU
+ *  2) Power down calling CPU
+ */
+static void __dead2 hand_over_to_lead_cpu(void)
+{
+	int ret;
+	unsigned int mpid = read_mpidr_el1() & MPID_MASK;
+	unsigned int core_pos = platform_get_core_pos(mpid);
+
+	VERBOSE("CPU%u: Hand over to lead CPU%u\n", core_pos,
+		platform_get_core_pos(lead_cpu_mpid));
+
+	/*
+	 * Power on lead CPU.
+	 * The entry point address passed as the 2nd argument of tftf_cpu_on()
+	 * doesn't matter because it will be overwritten by prepare_next_test().
+	 * Pass a NULL pointer to easily catch the problem in case something
+	 * goes wrong.
+	 */
+	ret = tftf_cpu_on(lead_cpu_mpid, 0, 0);
+	if (ret != PSCI_E_SUCCESS) {
+		ERROR("CPU%u: Failed to power on lead CPU%u (%d)\n",
+			core_pos, platform_get_core_pos(lead_cpu_mpid), ret);
+		panic();
+	}
+
+	/* Wait for lead CPU to be actually powered on */
+	while (!tftf_is_cpu_online(lead_cpu_mpid))
+		;
+
+	/*
+	 * Lead CPU has successfully booted, let's now power down the calling
+	 * core.
+	 */
+	tftf_cpu_off();
+	panic();
+}
+
+void __dead2 run_tests(void)
+{
+	unsigned int mpid = read_mpidr_el1() & MPID_MASK;
+	unsigned int core_pos = platform_get_core_pos(mpid);
+	unsigned int test_session_finished;
+	unsigned int cpus_cnt;
+
+	while (1) {
+		if (mpid == lead_cpu_mpid && (tftf_get_ref_cnt() == 0))
+			prepare_next_test();
+
+		/*
+		 * Increment the reference count to indicate that the CPU is
+		 * participating in the test.
+		 */
+		tftf_inc_ref_cnt();
+
+		/*
+		 * Mark the CPU's test result as "crashed". This is meant to be
+		 * overwritten by the actual test result when the CPU returns
+		 * from the test function into the framework. In case the CPU
+		 * crashes in the test (and thus, never returns from it), this
+		 * variable will hold the right value.
+		 */
+		test_results[core_pos] = TEST_RESULT_CRASHED;
+
+		/*
+		 * Jump to the test entrypoint for this core.
+		 * - For the lead CPU, it has been populated by
+		 *   prepare_next_test()
+		 * - For other CPUs, it has been populated by tftf_cpu_on() or
+		 *   tftf_try_cpu_on()
+		 */
+		while (test_entrypoint[core_pos] == 0)
+			;
+
+		test_results[core_pos] = test_entrypoint[core_pos]();
+		test_entrypoint[core_pos] = 0;
+
+		/*
+		 * Decrement the reference count to indicate that the CPU is not
+		 * participating in the test any longer.
+		 */
+		cpus_cnt = tftf_dec_ref_cnt();
+
+		/*
+		 * Last CPU to exit the test gets to do the necessary
+		 * bookkeeping and to report the overall test result.
+		 * Other CPUs shut down.
+		 */
+		if (cpus_cnt == 0) {
+			test_session_finished = close_test();
+			if (test_session_finished)
+				break;
+
+			if (mpid != lead_cpu_mpid) {
+				hand_over_to_lead_cpu();
+				bug_unreachable();
+			}
+		} else {
+			tftf_cpu_off();
+			panic();
+		}
+	}
+
+	tftf_exit();
+
+	/* Should never reach this point */
+	bug_unreachable();
+}
+
+u_register_t tftf_get_cpu_on_ctx_id(unsigned int core_pos)
+{
+	assert(core_pos < PLATFORM_CORE_COUNT);
+
+	return cpu_on_ctx_id_arr[core_pos];
+}
+
+void tftf_set_cpu_on_ctx_id(unsigned int core_pos, u_register_t context_id)
+{
+	assert(core_pos < PLATFORM_CORE_COUNT);
+
+	cpu_on_ctx_id_arr[core_pos] = context_id;
+}
+
+unsigned int tftf_is_rebooted(void)
+{
+	return test_is_rebooting;
+}
+
+/*
+ * Return 0 if the test session can be resumed
+ *        -1 otherwise.
+ */
+static int resume_test_session(void)
+{
+	test_ref_t test_to_run;
+	test_progress_t test_progress;
+	const test_case_t *next_test;
+
+	/* Get back on our feet. Where did we stop? */
+	tftf_get_test_to_run(&test_to_run);
+	tftf_get_test_progress(&test_progress);
+	assert(TEST_PROGRESS_IS_VALID(test_progress));
+
+	switch (test_progress) {
+	case TEST_READY:
+		/*
+		 * The TFTF has reset in the framework code, before the test
+		 * actually started.
+		 * Nothing to update, just start the test from scratch.
+		 */
+		break;
+
+	case TEST_IN_PROGRESS:
+		/*
+		 * The test crashed, i.e. it couldn't complete.
+		 * Update the test result in NVM then move to the next test.
+		 */
+		INFO("Test has crashed, moving to the next one\n");
+		tftf_testcase_set_result(current_testcase(),
+					TEST_RESULT_CRASHED,
+					0);
+		next_test = advance_to_next_test();
+		if (!next_test) {
+			INFO("No more tests\n");
+			return -1;
+		}
+		break;
+
+	case TEST_COMPLETE:
+		/*
+		 * The TFTF has reset in the framework code, after the test had
+		 * completed but before we finished the framework maintenance
+		 * required to move to the next test.
+		 *
+		 * In this case, we don't know the exact state of the data:
+		 * maybe we had the time to update the test result,
+		 * maybe we had the time to move to the next test.
+		 * We can't be sure so let's stay on the safe side and just
+		 * restart the test session from the beginning...
+		 */
+		NOTICE("The test framework has been interrupted in the middle "
+			"of critical maintenance operations.\n");
+		NOTICE("Can't recover execution.\n");
+		return -1;
+
+	case TEST_REBOOTING:
+		/*
+		 * Nothing to update about the test session, as we want to
+		 * re-enter the same test. Just remember that the test is
+		 * rebooting in case it queries this information.
+		 */
+		test_is_rebooting = 1;
+		break;
+
+	default:
+		bug_unreachable();
+	}
+
+	return 0;
+}
+
+/*
+ * C entry point in the TFTF.
+ * This function is executed by the primary CPU only.
+ */
+void __dead2 tftf_cold_boot_main(void)
+{
+	STATUS status;
+	int rc;
+
+	NOTICE("%s\n", TFTF_WELCOME_STR);
+	NOTICE("%s\n", build_message);
+	NOTICE("%s\n\n", version_string);
+
+#ifndef AARCH32
+	NOTICE("Running at NS-EL%u\n", IS_IN_EL(1) ? 1 : 2);
+#else
+	NOTICE("Running in AArch32 HYP mode\n");
+#endif
+
+	tftf_arch_setup();
+	tftf_platform_setup();
+	tftf_init_topology();
+
+	tftf_irq_setup();
+
+	rc = tftf_initialise_timer();
+	if (rc != 0) {
+		ERROR("Failed to initialize the timer subsystem (%d).\n", rc);
+		tftf_exit();
+	}
+
+	/* Enable the SGI used by the timer management framework */
+	tftf_irq_enable(IRQ_WAKE_SGI, GIC_HIGHEST_NS_PRIORITY);
+	enable_irq();
+
+	if (new_test_session()) {
+		NOTICE("Starting a new test session\n");
+		status = tftf_init_nvm();
+		if (status != STATUS_SUCCESS) {
+			/*
+			 * TFTF will have an undetermined behavior if its data
+			 * structures have not been initialised. There's no
+			 * point in continuing execution.
+			 */
+			ERROR("FATAL: Failed to initialise internal data structures in NVM.\n");
+			tftf_clean_nvm();
+			tftf_exit();
+		}
+	} else {
+		NOTICE("Resuming interrupted test session\n");
+		rc = resume_test_session();
+		if (rc < 0) {
+			tftf_report_generate();
+			tftf_clean_nvm();
+			tftf_exit();
+		}
+	}
+
+	/* Initialise the CPUs status map */
+	tftf_init_cpus_status_map();
+
+	/*
+	 * Detect power state format and get power state information for
+	 * a platform.
+	 */
+	tftf_init_pstate_framework();
+
+	/* The lead CPU is always the primary core. */
+	lead_cpu_mpid = read_mpidr_el1() & MPID_MASK;
+
+	/*
+	 * Hand over to lead CPU if required.
+	 * If the primary CPU is not the lead CPU for the first test then:
+	 *  1) Power on the lead CPU
+	 *  2) Power down the primary CPU
+	 */
+	if ((read_mpidr_el1() & MPID_MASK) != lead_cpu_mpid) {
+		hand_over_to_lead_cpu();
+		bug_unreachable();
+	}
+
+	/* Enter the test session */
+	run_tests();
+
+	/* Should never reach this point */
+	bug_unreachable();
+}
+
+void __dead2 tftf_exit(void)
+{
+	NOTICE("Exiting tests.\n");
+
+	/* Let the platform code clean up if required */
+	tftf_platform_end();
+
+	while (1)
+		wfi();
+}