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