|  | // 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" | 
|  |  | 
|  | #include <ktl/enforce.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++) { | 
|  | AutoPreemptDisabler preempt_disable; | 
|  | (*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(); | 
|  | 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 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(); | 
|  | 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(); | 
|  | 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") |