/** @file * * Copyright (c) 2013-2016, ARM Limited. All rights reserved. * * This program and the accompanying materials * are licensed and made available under the terms and conditions of the BSD License * which accompanies this distribution. The full text of the license may be found at * http://opensource.org/licenses/bsd-license.php * * THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, * WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. * **/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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 } /* TODO: Update lead CPU for next test */ 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() & 0xFFFFFF; 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() & 0xFFFFFF; 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(); /* * TODO: Make the lead CPU configurable from the tests description. * Each test should be able to specify its lead CPU in the tests.xml * file. For now, hard-code the lead CPU to always be the primary core. */ lead_cpu_mpid = read_mpidr_el1() & 0xFFFFFF; /* * 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() & MPIDR_AFFINITY_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(); }