| // 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 |
| // https://opensource.org/licenses/MIT |
| |
| #include <lib/unittest/unittest.h> |
| |
| #include <arch/arch_ops.h> |
| #include <arch/mp.h> |
| #include <fbl/alloc_checker.h> |
| #include <kernel/auto_preempt_disabler.h> |
| #include <kernel/cpu.h> |
| #include <kernel/dpc.h> |
| #include <kernel/event.h> |
| #include <ktl/array.h> |
| #include <ktl/atomic.h> |
| #include <ktl/unique_ptr.h> |
| |
| #include "tests.h" |
| |
| struct event_signal_from_dpc_context { |
| Dpc dpc; |
| Event event; |
| ktl::atomic<cpu_num_t> expected_cpu; |
| ktl::atomic<bool> dpc_started; |
| }; |
| |
| static void event_signal_from_dpc_check_cpu(Dpc* dpc) { |
| auto* const context = dpc->arg<event_signal_from_dpc_context>(); |
| context->dpc_started = true; |
| |
| // DPCs allow interrupts and blocking. |
| DEBUG_ASSERT(!arch_ints_disabled()); |
| DEBUG_ASSERT(!arch_blocking_disallowed()); |
| DEBUG_ASSERT(context->expected_cpu == arch_curr_cpu_num()); |
| |
| context->event.Signal(); |
| } |
| |
| static bool test_dpc_queue() { |
| BEGIN_TEST; |
| |
| static constexpr int kNumDPCs = 72; |
| |
| fbl::AllocChecker ac; |
| auto context = ktl::make_unique<ktl::array<event_signal_from_dpc_context, kNumDPCs>>(&ac); |
| ASSERT_TRUE(ac.check()); |
| |
| // Init all the DPCs and supporting context. |
| for (int i = 0; i < kNumDPCs; i++) { |
| (*context)[i].dpc_started = false; |
| } |
| |
| // Fire off DPCs. |
| for (int i = 0; i < kNumDPCs; i++) { |
| (*context)[i].dpc = |
| Dpc{&event_signal_from_dpc_check_cpu, reinterpret_cast<void*>(&(*context)[i])}; |
| interrupt_saved_state_t int_state = arch_interrupt_save(); |
| (*context)[i].expected_cpu = arch_curr_cpu_num(); |
| (*context)[i].dpc.Queue(/*reschedule=*/false); |
| arch_interrupt_restore(int_state); |
| } |
| for (int i = 0; i < kNumDPCs; i++) { |
| if ((*context)[i].dpc_started) { |
| // Once the DPC has started executing, we can reclaim the submitted Dpc. Zero it to |
| // try to check this. |
| (*context)[i].dpc = Dpc(); |
| } |
| } |
| for (int i = 0; i < kNumDPCs; i++) { |
| (*context)[i].event.Wait(); |
| } |
| |
| END_TEST; |
| } |
| |
| // Test that it's safe to repeatedly queue up the same DPC over and over. |
| static bool test_dpc_requeue() { |
| BEGIN_TEST; |
| |
| // Disable preemption to prevent the DCP worker, which is a deadline thread, |
| // from immediately preempting the test thread. This also ensures that the |
| // test thread remains on the same CPU as the DPC is enqueued on, othewise |
| // work stealing can move the test thread to another CPU while the DPC worker |
| // executes, resulting in a race between the Dpc destructor in the test thread |
| // and the DPC worker. |
| AutoPreemptDisabler<APDInitialState::PREEMPT_DISABLED> preempt_disable; |
| |
| ktl::atomic<uint64_t> actual_count = 0; |
| Dpc dpc_increment([](Dpc* d) { d->arg<ktl::atomic<uint64_t>>()->fetch_add(1); }, &actual_count); |
| |
| constexpr uint64_t kNumIterations = 10000; |
| uint64_t expected_count = 0; |
| for (unsigned i = 0; i < kNumIterations; ++i) { |
| // If we queue faster than the DPC worker thread can dequeue, the call may fail with |
| // ZX_ERR_ALREADY_EXISTS. That's OK, we just won't increment |expected_count| in that case. |
| zx_status_t status = dpc_increment.Queue(/* reschedule = */ true); |
| if (status == ZX_OK) { |
| ++expected_count; |
| } else { |
| ASSERT_EQ(status, ZX_ERR_ALREADY_EXISTS); |
| } |
| } |
| |
| // There might still be one DPC queued up for execution. Wait for it to "flush" the queue. |
| Event event_flush; |
| Dpc dpc_flush([](Dpc* d) { d->arg<Event>()->Signal(); }, &event_flush); |
| dpc_flush.Queue(/* reschedule = */ true); |
| event_flush.Wait(Deadline::no_slack(ZX_TIME_INFINITE)); |
| |
| ASSERT_EQ(actual_count.load(), expected_count); |
| |
| END_TEST; |
| } |
| |
| UNITTEST_START_TESTCASE(dpc_tests) |
| UNITTEST("basic test of dpc_queue", test_dpc_queue) |
| UNITTEST("repeatedly queue the same dpc", test_dpc_requeue) |
| UNITTEST_END_TESTCASE(dpc_tests, "dpc_tests", "Tests of DPCs") |