blob: abda3c84a879d24e1d6b7a35bab8a5bd68e28ae3 [file] [log] [blame]
// Copyright 2019 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
#include <lib/affine/ratio.h>
#include <lib/arch/intrin.h>
#include <lib/fit/defer.h>
#include <lib/unittest/unittest.h>
#include <lib/zircon-internal/macros.h>
#include <lib/zx/time.h>
#include <platform.h>
#include <kernel/auto_preempt_disabler.h>
#include <kernel/mp.h>
#include <ktl/atomic.h>
#include <ktl/bit.h>
#include "tests.h"
#include <ktl/enforce.h>
namespace {
bool mutex_spin_time_test(void) {
// We cannot run this test unless there are at least 2 CPUs currently online. Either find two
// cores we can use, or just skip the test with a warning message.
cpu_mask_t timer_mask;
cpu_mask_t spinner_mask;
cpu_mask_t avail_mask = mp_get_online_mask();
int avail_count = ktl::popcount(avail_mask);
if (avail_count < 2) {
printf("Insufficient cores online to run the mutex spin timeout tests. Skipping!\n");
for (timer_mask = 0x1; (timer_mask & avail_mask) == 0; timer_mask <<= 1)
for (spinner_mask = timer_mask << 1; (spinner_mask & avail_mask) == 0; spinner_mask <<= 1)
// No matter what happens from here on out, make sure we restore our main
// thread's priority and cpu affinity.
auto cleanup =
fit::defer([affinity = Thread::Current::Get()->GetCpuAffinity(),
priority = Thread::Current::Get()->scheduler_state().base_priority()]() {
constexpr zx::duration kTimeouts[] = {
zx::usec(0), zx::usec(50), zx::usec(250), zx::usec(750), zx::usec(5000),
const affine::Ratio ticks_to_time = platform_get_ticks_to_time_ratio();
struct Args {
DECLARE_MUTEX(Args) the_mutex;
zx::duration spin_max_duration;
ktl::atomic<bool> interlock{false};
} args;
// Our test thunk is very simple. One we are started, we disable preemption
// and then signal the timer thread via the interlock atomic. Once the timer
// thread has ack'ed our signal, we just grab and release the test mutex with
// the specified spin timeout.
auto thunk = [](void* ctx) -> int {
auto& args = *(static_cast<Args*>(ctx));
AutoPreemptDisabler ap_disabler;;
while (args.interlock.load() == true) {
Guard<Mutex> guard{&args.the_mutex, args.spin_max_duration.get()};
return 0;
// Boost our thread priority and lock ourselves down to a specific CPU before
// starting the test.
for (const auto& timeout : kTimeouts) {
zx::ticks start, end;
Thread* test_thread;
// Setup the timeout and create the test thread (but don't start it yet).
// Make sure that the thread runs on a different core from ours.
args.spin_max_duration = timeout;
test_thread = Thread::Create("mutex spin timeout", thunk, &args, HIGH_PRIORITY);
ASSERT_NONNULL(test_thread, "Failed to create test thread");
// Hold onto the mutex while we create a thread and time how long it takes for the mutex to
// become blocked.
AutoPreemptDisabler ap_disabler;
Guard<Mutex> guard{&args.the_mutex};
// Wait until the spinner thread is ready to go, then mark the start time
// and tell the spinner it is OK to proceed.
while (args.interlock.load() == false) {
start = zx::ticks(current_ticks());;
// Spin until we notice that the thread is blocked.
thread_state s;
while (true) {
Guard<MonitoredSpinLock, IrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
s = test_thread->state();
if (s == THREAD_BLOCKED) {
end = zx::ticks(current_ticks());
// Now that we are out of the lock, clean up the test thread and check our
// timing. We should have spun for at _least_ the time specified. For the
// benefit of a human test runner/observer, also print out how much over the
// limit we ended up. There is technically no upper bound to this number,
// but we would like to observe the overshoot amount as being "reasonable"
// in an unloaded manual test environment.
zx_status_t status = test_thread->Join(nullptr, current_time() + ZX_SEC(30));
ASSERT_EQ(status, ZX_OK, "test thread failed to exit!");
zx::duration actual_spin_time(ticks_to_time.Scale((end - start).get()));
EXPECT_GE(actual_spin_time.get(), timeout.get(), "Didn't spin for long enough!");
if (timeout.get() > 0) {
int64_t overshoot = (((actual_spin_time - timeout).get()) * 10000) / timeout.get();
printf("Target %7ld nSec, Actual %7ld nSec. Overshot by %ld.%02ld%%.\n", timeout.get(),
actual_spin_time.get(), overshoot / 100, overshoot % 100);
} else {
printf("\nTarget %7ld nSec, Actual %7ld nSec.\n", timeout.get(), actual_spin_time.get());
} // namespace
UNITTEST("Mutex spin timeouts", (mutex_spin_time_test))
UNITTEST_END_TESTCASE(mutex_spin_time_tests, "mutex_spin_time", "mutex_spin_time tests")