tests/arch/common: Add tests for the arch timing API
Simple tests to check the sanity of implementations. As with any tests
involving time, a constant "tolerance" of 10% is used in some of the
measurements.
Checks are basically answering the questions 1) "is time always
increasing?" and 2) "at a somewhat constant pace?". And some tests for
the time conversion, answering "is the elapsed time in nanoseconds
somewhat coherent?".
As timing and emulation are a somewhat dangerous combination,
`testcase.yaml` exclude CONFIG_QEMU_TARGET=y from tests.
Signed-off-by: Ederson de Souza <ederson.desouza@intel.com>
diff --git a/tests/arch/common/timing/CMakeLists.txt b/tests/arch/common/timing/CMakeLists.txt
new file mode 100644
index 0000000..a032cbc
--- /dev/null
+++ b/tests/arch/common/timing/CMakeLists.txt
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: Apache-2.0
+
+cmake_minimum_required(VERSION 3.20.0)
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(arch_timing)
+
+target_sources(app PRIVATE src/main.c)
diff --git a/tests/arch/common/timing/prj.conf b/tests/arch/common/timing/prj.conf
new file mode 100644
index 0000000..2791ee2
--- /dev/null
+++ b/tests/arch/common/timing/prj.conf
@@ -0,0 +1,4 @@
+CONFIG_ZTEST=y
+CONFIG_ZTEST_NEW_API=y
+CONFIG_TIMING_FUNCTIONS=y
+CONFIG_FPU=y
diff --git a/tests/arch/common/timing/src/main.c b/tests/arch/common/timing/src/main.c
new file mode 100644
index 0000000..275b16a
--- /dev/null
+++ b/tests/arch/common/timing/src/main.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2022 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <zephyr/ztest.h>
+#include <zephyr/kernel.h>
+#include <zephyr/sys/arch_interface.h>
+
+#if defined(CONFIG_SMP) && CONFIG_MP_NUM_CPUS > 1
+#define SMP_TEST
+#endif
+
+#ifdef SMP_TEST
+#define THREADS_NUM CONFIG_MP_NUM_CPUS
+#define STACK_SIZE 1024
+#define PRIORITY 7
+
+static struct k_thread threads[THREADS_NUM];
+static K_THREAD_STACK_ARRAY_DEFINE(tstack, THREADS_NUM, STACK_SIZE);
+#endif
+
+#define WAIT_US 1000
+#define WAIT_NS (WAIT_US * 1000)
+#define TOLERANCE 0.1
+
+static void perform_tests(void)
+{
+ timing_t start, middle, end;
+ uint64_t diff1, diff2, diff_all, freq_hz;
+ uint64_t diff1_ns, diff2_ns, diff_all_ns, diff_avg_ns;
+ uint32_t freq_mhz;
+
+ arch_timing_start();
+
+ start = arch_timing_counter_get();
+ k_busy_wait(WAIT_US);
+ middle = arch_timing_counter_get();
+ k_busy_wait(WAIT_US);
+ end = arch_timing_counter_get();
+
+ /* Time shouldn't stop or go backwards */
+ diff1 = arch_timing_cycles_get(&start, &middle);
+ diff2 = arch_timing_cycles_get(&middle, &end);
+ diff_all = arch_timing_cycles_get(&start, &end);
+ zassert_true(diff1 > 0, NULL);
+ zassert_true(diff2 > 0, NULL);
+ zassert_true(diff_all > 0, NULL);
+
+ /* Differences shouldn't be so different, as both are spaced by
+ * k_busy_wait(WAIT_US).
+ */
+ zassert_within(diff1, diff2, diff1 * TOLERANCE, NULL);
+ zassert_within(diff_all, diff1 + diff2, (diff1 + diff2) * TOLERANCE,
+ NULL);
+
+ freq_hz = arch_timing_freq_get();
+ freq_mhz = arch_timing_freq_get_mhz();
+ zassert_equal(freq_mhz, (uint32_t)(freq_hz / 1000000ULL), NULL);
+
+ diff1_ns = arch_timing_cycles_to_ns(diff1);
+ diff2_ns = arch_timing_cycles_to_ns(diff2);
+ diff_all_ns = arch_timing_cycles_to_ns(diff_all);
+
+ /* Ensure the differences are close to 100us */
+ zassert_within(diff1_ns, WAIT_NS, WAIT_NS * TOLERANCE, NULL);
+ zassert_within(diff2_ns, WAIT_NS, WAIT_NS * TOLERANCE, NULL);
+ zassert_within(diff_all_ns, 2 * WAIT_NS, 2 * WAIT_NS * TOLERANCE, NULL);
+
+ diff_avg_ns = arch_timing_cycles_to_ns_avg(diff1 + diff2, 2);
+ zassert_within(diff_avg_ns, WAIT_NS, WAIT_NS * TOLERANCE, NULL);
+
+ arch_timing_stop();
+}
+
+void *timing_setup(void)
+{
+ arch_timing_init();
+
+ return NULL;
+}
+
+ZTEST(arch_timing, test_arch_timing)
+{
+ perform_tests();
+ /* Run tests again to ensure nothing breaks after arch_timing_stop */
+ perform_tests();
+}
+
+#ifdef SMP_TEST
+static void thread_entry(void *p1, void *p2, void *p3)
+{
+ ARG_UNUSED(p1);
+ ARG_UNUSED(p2);
+ ARG_UNUSED(p3);
+
+ perform_tests();
+ /* Run tests again to ensure nothing breaks after arch_timing_stop */
+ perform_tests();
+}
+
+ZTEST(arch_timing, test_arch_timing_smp)
+{
+ int i;
+
+ for (i = 0; i < THREADS_NUM; i++) {
+ k_thread_create(&threads[i], tstack[i], STACK_SIZE,
+ thread_entry, NULL, NULL, NULL,
+ PRIORITY, 0, K_FOREVER);
+ k_thread_cpu_mask_enable(&threads[i], i);
+ k_thread_start(&threads[i]);
+ }
+
+ for (i = 0; i < THREADS_NUM; i++)
+ k_thread_join(&threads[i], K_FOREVER);
+}
+#else
+ZTEST(arch_timing, test_arch_timing_smp)
+{
+ ztest_test_skip();
+}
+#endif
+
+ZTEST_SUITE(arch_timing, NULL, timing_setup, NULL, NULL, NULL);
diff --git a/tests/arch/common/timing/testcase.yaml b/tests/arch/common/timing/testcase.yaml
new file mode 100644
index 0000000..30b3f42
--- /dev/null
+++ b/tests/arch/common/timing/testcase.yaml
@@ -0,0 +1,9 @@
+common:
+ filter: CONFIG_ARCH_HAS_TIMING_FUNCTIONS
+tests:
+ arch.common.timing:
+ filter: CONFIG_MP_NUM_CPUS == 1 and not CONFIG_QEMU_TARGET
+ arch.common.timing.smp:
+ filter: CONFIG_MP_NUM_CPUS > 1 and not CONFIG_QEMU_TARGET
+ extra_configs:
+ - CONFIG_SCHED_CPU_MASK=y