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();
+}