blob: b178cd6a21e4f55c423147a24e0d99950888d1fa [file]
// Copyright 2026 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <lib/affine/ratio.h>
#include <lib/boot-options/boot-options.h>
#include <lib/fit/defer.h>
#include <lib/unittest/unittest.h>
#include <dev/timer/arm_generic.h>
#include <kernel/percpu.h>
#include <kernel/scheduler.h>
#include <kernel/thread.h>
#include <ktl/atomic.h>
#include <ktl/bit.h> // for popcount? In standard C++ it is <bit>, in ktl it might be different.
#include <ktl/limits.h>
#include <platform/timer.h> // for ticks_per_second
// We need to declare things that are not in public headers if they are needed.
// ticks_per_second() is in <platform/timer.h>
// timer_get_ticks_to_time_ratio() is in <platform/timer.h>
namespace {
[[maybe_unused]] constexpr uint32_t kMinTestFreq = 1;
[[maybe_unused]] constexpr uint32_t kMaxTestFreq = ktl::numeric_limits<uint32_t>::max();
[[maybe_unused]] constexpr uint32_t kCurTestFreq = 0;
inline uint64_t abs_int64(int64_t a) { return (a > 0) ? a : static_cast<uint64_t>(-a); }
bool test_time_conversion_check_result(uint64_t a, uint64_t b, uint64_t limit) {
BEGIN_TEST;
if (a != b) {
uint64_t diff = abs_int64(a - b);
ASSERT_LE(diff, limit);
}
END_TEST;
}
bool test_time_to_ticks(uint32_t cntfrq) {
BEGIN_TEST;
affine::Ratio time_to_ticks;
if (cntfrq == kCurTestFreq) {
uint64_t tps = ticks_per_second();
ASSERT_LE(tps, ktl::numeric_limits<uint32_t>::max());
cntfrq = static_cast<uint32_t>(tps);
time_to_ticks = timer_get_ticks_to_time_ratio().Inverse();
} else {
time_to_ticks = arm_generic_timer_compute_conversion_factors(cntfrq).Inverse();
}
constexpr uint64_t VECTORS[] = {
0,
1,
60 * 60 * 24,
60 * 60 * 24 * 365,
60 * 60 * 24 * (365 * 10 + 2),
60ULL * 60 * 24 * (365 * 100 + 2),
};
for (auto vec : VECTORS) {
uint64_t cntpct = time_to_ticks.Scale(vec);
constexpr uint32_t nanos_per_sec = ZX_SEC(1);
uint64_t expected_cntpct = ((uint64_t)cntfrq * vec + (nanos_per_sec / 2)) / nanos_per_sec;
if (!test_time_conversion_check_result(cntpct, expected_cntpct, 1)) {
printf("FAIL: zx_time_to_ticks(%" PRIu64 "): got %" PRIu64 ", expect %" PRIu64 "\n", vec,
cntpct, expected_cntpct);
ASSERT_TRUE(false);
}
}
END_TEST;
}
bool test_ticks_to_time(uint32_t cntfrq) {
BEGIN_TEST;
affine::Ratio ticks_to_time;
if (cntfrq == kCurTestFreq) {
uint64_t tps = ticks_per_second();
ASSERT_LE(tps, ktl::numeric_limits<uint32_t>::max());
cntfrq = static_cast<uint32_t>(tps);
ticks_to_time = timer_get_ticks_to_time_ratio();
} else {
ticks_to_time = arm_generic_timer_compute_conversion_factors(cntfrq);
}
constexpr uint64_t VECTORS[] = {
1,
60 * 60 * 24,
60 * 60 * 24 * 365,
60 * 60 * 24 * (365 * 10 + 2),
60ULL * 60 * 24 * (365 * 50 + 2),
};
for (auto vec : VECTORS) {
zx_time_t expected_zx_time = ZX_SEC(vec);
uint64_t cntpct = (uint64_t)cntfrq * vec;
zx_time_t zx_time = ticks_to_time.Scale(cntpct);
const uint64_t limit = (1000 * 1000 + cntfrq - 1) / cntfrq;
if (!test_time_conversion_check_result(zx_time, expected_zx_time, limit)) {
printf("ticks_to_zx_time(0x%" PRIx64 "): got 0x%" PRIx64 ", expect 0x%" PRIx64 "\n", cntpct,
static_cast<uint64_t>(zx_time), static_cast<uint64_t>(expected_zx_time));
ASSERT_TRUE(false);
}
}
END_TEST;
}
// Verify that the event stream will break CPUs out of WFE.
//
// Start one thread for each CPU that's online and active. Each thread will then disable
// interrupts and issue a series of WFEs. If the event stream is working as expected, each thread
// will eventually complete its series of WFEs and terminate. If the event stream is not working
// as expected, one or more threads will hang.
bool test_event_stream() {
BEGIN_TEST;
if (!BootOptions::Get()->arm64_event_stream_enabled) {
printf("event stream disabled, skipping test\n");
END_TEST;
}
struct Args {
ktl::atomic<uint32_t> waiting{0};
};
auto func = [](void* args_) -> int {
auto* args = reinterpret_cast<Args*>(args_);
{
InterruptDisableGuard guard;
// Signal that we are ready.
args->waiting.fetch_sub(1);
// Wait until everyone else is ready.
while (args->waiting.load() > 0) {
}
// If the event stream is working, it (or something else) will break us out on each iteration.
for (int i = 0; i < 1000; ++i) {
// The SEVL sets the event flag for this CPU. The first WFE consumes the now set event
// flag. By setting then consuming, we can be sure the second WFE will actually wait for an
// event.
__asm__ volatile("sevl;wfe;wfe");
}
}
printf("cpu-%u done\n", arch_curr_cpu_num());
return 0;
};
Args args;
Thread* threads[SMP_MAX_CPUS]{};
// How many online+active CPUs do we have?
uint32_t num_cpus = ktl::popcount(mp_get_online_mask() & Scheduler::PeekActiveMask());
args.waiting.store(num_cpus);
// Create a thread bound to each online+active CPU, but don't start them just yet.
cpu_num_t last = 0;
for (cpu_num_t i = 0; i < percpu::processor_count(); ++i) {
if (mp_is_cpu_online(i) && Scheduler::PeekIsActive(i)) {
threads[i] = Thread::Create("test_event_stream", func, &args, DEFAULT_PRIORITY);
threads[i]->SetCpuAffinity(cpu_num_to_mask(i));
last = i;
}
}
// Because these threads have hard affinity and will disable interrupts we need to take care in
// how we start them. If we start one that's bound to our current CPU, we may get preempted
// deadlock. To avoid this, bind the current thread to the *last* online+active CPU.
const cpu_mask_t orig_mask = Thread::Current::Get()->GetCpuAffinity();
Thread::Current::Get()->SetCpuAffinity(cpu_num_to_mask(last));
auto restore_mask =
fit::defer([&orig_mask]() { Thread::Current::Get()->SetCpuAffinity(orig_mask); });
// Now that we're running on the last online+active CPU we can simply start them in order.
for (cpu_num_t i = 0; i < percpu::processor_count(); ++i) {
if (threads[i] != nullptr) {
threads[i]->Resume();
}
}
// Finally, wait for them to complete.
for (size_t i = 0; i < percpu::processor_count(); ++i) {
if (threads[i] != nullptr) {
threads[i]->Join(nullptr, ZX_TIME_INFINITE);
}
}
END_TEST;
}
} // namespace
UNITTEST_START_TESTCASE(arm_clock_tests)
UNITTEST("Time --> Ticks (min freq)", []() -> bool { return test_time_to_ticks(kMinTestFreq); })
UNITTEST("Time --> Ticks (max freq)", []() -> bool { return test_time_to_ticks(kMaxTestFreq); })
UNITTEST("Time --> Ticks (cur freq)", []() -> bool { return test_time_to_ticks(kCurTestFreq); })
UNITTEST("Ticks --> Time (min freq)", []() -> bool { return test_ticks_to_time(kMinTestFreq); })
UNITTEST("Ticks --> Time (max freq)", []() -> bool { return test_ticks_to_time(kMaxTestFreq); })
UNITTEST("Ticks --> Time (cur freq)", []() -> bool { return test_ticks_to_time(kCurTestFreq); })
UNITTEST("Event Stream", test_event_stream)
UNITTEST_END_TESTCASE(arm_clock_tests, "arm_clock", "Tests for ARM tick count and current time")