| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <lib/arch/intrin.h> |
| #include <lib/kconcurrent/seqlock.h> |
| #include <lib/unittest/unittest.h> |
| |
| #include <kernel/mp.h> |
| #include <ktl/bit.h> |
| #include <ktl/type_traits.h> |
| |
| #include <ktl/enforce.h> |
| |
| namespace { |
| |
| using SyncOpt = ::concurrent::SyncOpt; |
| |
| template <SyncOpt kSyncOpt> |
| struct SeqLockWrapper; |
| |
| template <> |
| struct SeqLockWrapper<SyncOpt::AcqRelOps> { |
| DECLARE_SEQLOCK_ACQREL_SYNC(SeqLockWrapper) seq; |
| }; |
| |
| template <> |
| struct SeqLockWrapper<SyncOpt::Fence> { |
| DECLARE_SEQLOCK_FENCE_SYNC(SeqLockWrapper) seq; |
| }; |
| |
| class CurrentCpuPinner { |
| public: |
| CurrentCpuPinner() { |
| interrupt_saved_state_t interrupt_state = arch_interrupt_save(); |
| |
| prev_affinity_ = Thread::Current::Get()->GetCpuAffinity(); |
| pin_mask_ = cpu_num_to_mask(arch_curr_cpu_num()); |
| Thread::Current::Get()->SetCpuAffinity(pin_mask_); |
| |
| arch_interrupt_restore(interrupt_state); |
| } |
| |
| ~CurrentCpuPinner() { ReleasePin(); } |
| |
| void ReleasePin() { |
| if (pin_mask_ != 0) { |
| Thread::Current::Get()->SetCpuAffinity(prev_affinity_); |
| pin_mask_ = 0; |
| } |
| } |
| |
| cpu_mask_t other_cpus_mask() const { return ~pin_mask_; } |
| |
| private: |
| cpu_mask_t prev_affinity_{0}; |
| cpu_mask_t pin_mask_{0}; |
| }; |
| |
| template <typename LockPolicy, SyncOpt kSyncOpt> |
| struct Test { |
| static bool UncontestedRead() { |
| BEGIN_TEST; |
| |
| static_assert(ktl::is_same_v<LockPolicy, SharedIrqSave> || |
| ktl::is_same_v<LockPolicy, SharedNoIrqSave>); |
| constexpr bool kExpectIrqsDisabled = ktl::is_same_v<LockPolicy, SharedIrqSave>; |
| |
| SeqLockWrapper<kSyncOpt> wrapper; |
| auto& seq = wrapper.seq; |
| |
| // Observe the lock's initial sequence number. It should not change over the |
| // course of these tests. |
| typename SeqLock<kSyncOpt>::SequenceNumber initial_num = seq.lock().seq_num(); |
| |
| // Deliberately initialize this as true. We want to test to be sure that the |
| // guard unconditionally sets it's state to false as we enter the guard. |
| bool transaction_success{true}; |
| { |
| ASSERT_FALSE( |
| arch_ints_disabled()); // interrupts should be enabled before we enter the guard. |
| ASSERT_TRUE(transaction_success); |
| |
| // Enter the guard. Interrupt enabled/disabled state should match what is |
| // expected based on the policy. Transaction_success should be now have been |
| // explicitly set to false. |
| lockdep::Guard<SeqLock<kSyncOpt>, LockPolicy> guard{&seq, transaction_success}; |
| ASSERT_EQ(kExpectIrqsDisabled, arch_ints_disabled()); |
| ASSERT_FALSE(transaction_success); |
| ASSERT_EQ(initial_num, seq.lock().seq_num()); |
| |
| // Now let the guard go out of scope. |
| } |
| |
| // Interrupts should be enabled (if they had been disabled), and the |
| // transaction should have succeeded. |
| ASSERT_FALSE(arch_ints_disabled()); |
| ASSERT_TRUE(transaction_success); |
| ASSERT_EQ(initial_num, seq.lock().seq_num()); |
| |
| END_TEST; |
| } |
| |
| static bool UncontestedWrite() { |
| BEGIN_TEST; |
| |
| static_assert(ktl::is_same_v<LockPolicy, ExclusiveIrqSave> || |
| ktl::is_same_v<LockPolicy, ExclusiveNoIrqSave>); |
| SeqLockWrapper<kSyncOpt> wrapper; |
| auto& seq = wrapper.seq; |
| |
| // Observe the lock's initial sequence number. It should go up by exactly one |
| // every time we enter or exit the lock. |
| typename SeqLock<kSyncOpt>::SequenceNumber initial_num = seq.lock().seq_num(); |
| |
| // interrupts should be enabled and blocking should be allowed before we |
| // enter the guard. |
| ASSERT_FALSE(arch_ints_disabled()); |
| ASSERT_FALSE(arch_blocking_disallowed()); |
| ASSERT_EQ(initial_num, seq.lock().seq_num()); |
| |
| // If we are using the IRQ save version of the guard, then we expect it to |
| // disable interrupts and disallow blocking for us. The NoIrqSave version |
| // is expected to DEBUG_ASSERT if interrupts are not already disabled, |
| // meaning that we need to take care of this ourselves, but we expect it to |
| // make sure that blocking is disallowed while we are in the guard. |
| [[maybe_unused]] interrupt_saved_state_t interrupt_state; |
| if constexpr (ktl::is_same_v<LockPolicy, ExclusiveNoIrqSave>) { |
| interrupt_state = arch_interrupt_save(); |
| } |
| |
| { |
| // Enter the guard. Interrupts should now be disabled, and blocking |
| // disallowed. The lock's sequence number should have gone up by one. |
| lockdep::Guard<SeqLock<kSyncOpt>, LockPolicy> guard{&seq}; |
| ASSERT_TRUE(arch_ints_disabled()); |
| ASSERT_TRUE(arch_blocking_disallowed()); |
| ASSERT_EQ(initial_num + 1, seq.lock().seq_num()); |
| |
| // Now let the guard go out of scope. |
| } |
| |
| // Restore interrupts if we had manually disabled them. |
| if constexpr (ktl::is_same_v<LockPolicy, ExclusiveNoIrqSave>) { |
| arch_interrupt_restore(interrupt_state); |
| } |
| |
| // Interrupts should now be enabled and blocking allowed again. The seq |
| // number should be 2 more than the initial number. |
| ASSERT_FALSE(arch_ints_disabled()); |
| ASSERT_FALSE(arch_blocking_disallowed()); |
| ASSERT_EQ(initial_num + 2, seq.lock().seq_num()); |
| |
| END_TEST; |
| } |
| |
| static bool ContestedTest() { |
| BEGIN_TEST; |
| |
| static_assert(ktl::is_same_v<LockPolicy, SharedIrqSave> || |
| ktl::is_same_v<LockPolicy, SharedNoIrqSave> || |
| ktl::is_same_v<LockPolicy, ExclusiveIrqSave> || |
| ktl::is_same_v<LockPolicy, ExclusiveNoIrqSave>); |
| |
| constexpr bool SharedTest = |
| ktl::is_same_v<LockPolicy, SharedIrqSave> || ktl::is_same_v<LockPolicy, SharedNoIrqSave>; |
| |
| SeqLockWrapper<kSyncOpt> wrapper; |
| auto& seq = wrapper.seq; |
| |
| if constexpr (SharedTest) { |
| // If we are testing shared contention, start with the simple test. Start a |
| // read transaction, but then have a "writer" enter the lock exclusively |
| // during the read transaction. The transaction should fail. Note: to keep |
| // things simple, we don't actually need or want to spin a thread for the |
| // writer. Instead, we simply simulate one by accessing the lock directly |
| // with static thread analysis checking disabled. |
| bool transaction_success{true}; |
| { |
| // Enter the guard. |
| ASSERT_TRUE(transaction_success); |
| lockdep::Guard<SeqLock<kSyncOpt>, LockPolicy> guard{&seq, transaction_success}; |
| ASSERT_FALSE(transaction_success); |
| |
| // Have a "writer" enter the lock exclusively |
| [&seq]() __TA_NO_THREAD_SAFETY_ANALYSIS { seq.lock().Acquire(); }(); |
| |
| // Let the guard go out of scope. |
| } |
| |
| // Go ahead and release the exclusive access to the lock. |
| [&seq]() __TA_NO_THREAD_SAFETY_ANALYSIS { seq.lock().Release(); }(); |
| |
| // The transaction should have failed. |
| ASSERT_FALSE(transaction_success); |
| } |
| |
| // Now check to make sure that guards (either shared or exclusive) cannot be |
| // entered when the lock is already held exclusively. Create a thread, and |
| // wait until know that the thread is about to enter the guard. Then wait just |
| // a bit longer and verify that the thread has still not managed to enter the |
| // guard. Finally, we release the exclusive hold we have on the lock and |
| // verify that the thread is able to make it through the guard, and in the |
| // case that the thread is using a shared guard, that the read transaction is |
| // reported as a success. |
| // |
| // Note: This is a best effort test, and contains false-negative potential. |
| // Just because the thread had not managed to make it into the guard in X |
| // units of time, does not mean that it won't eventually make it in. There is |
| // simply no way with a runtime unit-test to _prove_ that exclusion will occur |
| // until the lock is released. |
| // |
| if (uint32_t cpus_online = ktl::popcount(mp_get_online_mask()); cpus_online < 2) { |
| printf("Skipping Contested %s SeqLock test. There is only %u CPU online\n", |
| SharedTest ? "Read" : "Write", cpus_online); |
| } else { |
| enum class State : uint32_t { |
| NotStarted, |
| EnteringGuard, |
| GuardEntered, |
| }; |
| static_assert(ktl::atomic<State>::is_always_lock_free); |
| |
| using InstrumentedLockType = decltype(seq); |
| struct TestParams { |
| InstrumentedLockType& seq; |
| ktl::atomic<State> state{State::NotStarted}; |
| } params{seq}; |
| |
| // Pin ourselves to our current CPU during the test. |
| CurrentCpuPinner cpu_pinner; |
| |
| // Create and resume the thread, making certain that it must run on a CPU other than ours. |
| Thread* test_thread; |
| if constexpr (SharedTest) { |
| test_thread = Thread::Create( |
| "SeqLock ContestedRead Test", |
| +[](void* arg) -> int { |
| TestParams& params = *reinterpret_cast<TestParams*>(arg); |
| |
| params.state.store(State::EnteringGuard); |
| bool transaction_success; |
| { |
| lockdep::Guard<SeqLock<kSyncOpt>, LockPolicy> guard{¶ms.seq, |
| transaction_success}; |
| params.state.store(State::GuardEntered); |
| } |
| |
| return transaction_success ? 1 : 0; |
| }, |
| ¶ms, DEFAULT_PRIORITY); |
| } else { |
| test_thread = Thread::Create( |
| "SeqLock ContestedWrite Test", |
| +[](void* arg) -> int { |
| TestParams& params = *reinterpret_cast<TestParams*>(arg); |
| |
| params.state.store(State::EnteringGuard); |
| |
| // The NoIrqSave version of this guard is going to demand that |
| // interrupts have already been disabled with a DEBUG_ASSERT. If |
| // that is the version we are using, make sure to manually disable |
| // and re-enable interrupts. |
| [[maybe_unused]] interrupt_saved_state_t interrupt_state; |
| if constexpr (ktl::is_same_v<LockPolicy, ExclusiveNoIrqSave>) { |
| interrupt_state = arch_interrupt_save(); |
| } |
| |
| { |
| lockdep::Guard<SeqLock<kSyncOpt>, LockPolicy> guard{¶ms.seq}; |
| params.state.store(State::GuardEntered); |
| } |
| |
| if constexpr (ktl::is_same_v<LockPolicy, ExclusiveNoIrqSave>) { |
| arch_interrupt_restore(interrupt_state); |
| } |
| |
| return 1; |
| }, |
| ¶ms, DEFAULT_PRIORITY); |
| } |
| ASSERT_NONNULL(test_thread); |
| test_thread->SetCpuAffinity(cpu_pinner.other_cpus_mask()); |
| |
| // Hold the lock exclusively |
| lockdep::Guard<SeqLock<kSyncOpt>, ExclusiveIrqSave> guard{¶ms.seq}; |
| test_thread->Resume(); |
| |
| // Wait for the thread to start to enter the guard. |
| while (params.state.load() != State::EnteringGuard) { |
| arch::Yield(); |
| } |
| |
| // Wait for a bit longer, then verify that the thread is still attempting to |
| // enter the guard. |
| zx_time_t deadline = current_time() + ZX_MSEC(500); |
| while (deadline > current_time()) { |
| arch::Yield(); |
| } |
| EXPECT_EQ(State::EnteringGuard, params.state.load()); |
| |
| // Release the lock and wait for the thread to indicate that it has entered |
| // the guard. |
| guard.Release(); |
| while (params.state.load() != State::GuardEntered) { |
| arch::Yield(); |
| } |
| |
| // Join the thread, and make sure that the read transaction was successful |
| // (if this was a shared guard test) |
| int retcode; |
| cpu_pinner.ReleasePin(); |
| test_thread->Join(&retcode, ZX_TIME_INFINITE); |
| ASSERT_EQ(1, retcode); |
| } |
| |
| END_TEST; |
| } |
| }; |
| |
| // Sadly, these aliases are needed in order to be able to invoke the UNITTEST |
| // preprocessor macro without having it asplode because of the `,` in the |
| // template argument list for the test selection. |
| template <typename LockPolicy> |
| using TestAcqRel = Test<LockPolicy, SyncOpt::AcqRelOps>; |
| |
| template <typename LockPolicy> |
| using TestFence = Test<LockPolicy, SyncOpt::Fence>; |
| } // namespace |
| |
| UNITTEST_START_TESTCASE(seqlock_tests) |
| UNITTEST("UncontestedRead<IrqSave, AcqRel>", TestAcqRel<SharedIrqSave>::UncontestedRead) |
| UNITTEST("UncontestedRead<NoIrqSave, AcqRel>", TestAcqRel<SharedNoIrqSave>::UncontestedRead) |
| UNITTEST("UncontestedWrite<IrqSave, AcqRel>", TestAcqRel<ExclusiveIrqSave>::UncontestedWrite) |
| UNITTEST("UncontestedWrite<NoIrqSave, AcqRel>", TestAcqRel<ExclusiveNoIrqSave>::UncontestedWrite) |
| UNITTEST("ContestedRead<IrqSave, AcqRel>", TestAcqRel<SharedIrqSave>::ContestedTest) |
| UNITTEST("ContestedRead<NoIrqSave, AcqRel>", TestAcqRel<SharedNoIrqSave>::ContestedTest) |
| UNITTEST("ContestedWrite<IrqSave, AcqRel>", TestAcqRel<ExclusiveIrqSave>::ContestedTest) |
| UNITTEST("ContestedWrite<NoIrqSave, AcqRel>", TestAcqRel<ExclusiveNoIrqSave>::ContestedTest) |
| |
| UNITTEST("UncontestedRead<IrqSave, Fence>", TestFence<SharedIrqSave>::UncontestedRead) |
| UNITTEST("UncontestedRead<NoIrqSave, Fence>", TestFence<SharedNoIrqSave>::UncontestedRead) |
| UNITTEST("UncontestedWrite<IrqSave, Fence>", TestFence<ExclusiveIrqSave>::UncontestedWrite) |
| UNITTEST("UncontestedWrite<NoIrqSave, Fence>", TestFence<ExclusiveNoIrqSave>::UncontestedWrite) |
| UNITTEST("ContestedRead<IrqSave, Fence>", TestFence<SharedIrqSave>::ContestedTest) |
| UNITTEST("ContestedRead<NoIrqSave, Fence>", TestFence<SharedNoIrqSave>::ContestedTest) |
| UNITTEST("ContestedWrite<IrqSave, Fence>", TestFence<ExclusiveIrqSave>::ContestedTest) |
| UNITTEST("ContestedWrite<NoIrqSave, Fence>", TestFence<ExclusiveNoIrqSave>::ContestedTest) |
| UNITTEST_END_TESTCASE(seqlock_tests, "seqlock", "SeqLock Guard Tests") |