| // Copyright 2016 The Fuchsia Authors |
| // Copyright (c) 2012 Travis Geiselbrecht |
| // |
| // 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 "tests.h" |
| |
| #include <arch/ops.h> |
| #include <assert.h> |
| #include <err.h> |
| #include <fbl/algorithm.h> |
| #include <kernel/event.h> |
| #include <kernel/mp.h> |
| #include <kernel/thread.h> |
| #include <lib/unittest/unittest.h> |
| #include <stdio.h> |
| #include <trace.h> |
| #include <zircon/types.h> |
| |
| #define LOCAL_TRACE 0 |
| |
| #define TEST_RUNS 1000 |
| |
| static void inorder_count_task(void* raw_context) { |
| ASSERT(arch_ints_disabled()); |
| ASSERT(arch_blocking_disallowed()); |
| int* inorder_counter = (int*)raw_context; |
| uint cpu_num = arch_curr_cpu_num(); |
| |
| int oldval = atomic_add(inorder_counter, 1); |
| ASSERT(oldval == (int)cpu_num); |
| LTRACEF(" CPU %u checked in\n", cpu_num); |
| } |
| |
| static void counter_task(void* raw_context) { |
| ASSERT(arch_ints_disabled()); |
| ASSERT(arch_blocking_disallowed()); |
| int* counter = (int*)raw_context; |
| atomic_add(counter, 1); |
| } |
| |
| static int deadlock_test_thread(void* arg) { |
| event_t* gate = (event_t*)arg; |
| event_wait(gate); |
| |
| int counter = 0; |
| arch_disable_ints(); |
| mp_sync_exec(MP_IPI_TARGET_ALL_BUT_LOCAL, 0, counter_task, &counter); |
| arch_enable_ints(); |
| return 0; |
| } |
| |
| static void deadlock_test(void) { |
| /* Test for a deadlock caused by multiple CPUs broadcasting concurrently */ |
| |
| event_t gate = EVENT_INITIAL_VALUE(gate, false, 0); |
| |
| thread_t* threads[5] = {0}; |
| for (uint i = 0; i < fbl::count_of(threads); ++i) { |
| threads[i] = thread_create("sync_ipi_deadlock", deadlock_test_thread, &gate, DEFAULT_PRIORITY); |
| if (!threads[i]) { |
| TRACEF(" failed to create thread\n"); |
| goto cleanup; |
| } |
| thread_resume(threads[i]); |
| } |
| |
| event_signal(&gate, true); |
| |
| cleanup: |
| for (uint i = 0; i < fbl::count_of(threads); ++i) { |
| if (threads[i]) { |
| thread_join(threads[i], NULL, ZX_TIME_INFINITE); |
| } |
| } |
| event_destroy(&gate); |
| }; |
| |
| static bool sync_ipi_tests() { |
| BEGIN_TEST; |
| |
| uint num_cpus = arch_max_num_cpus(); |
| uint online = mp_get_online_mask(); |
| if (online != (1U << num_cpus) - 1) { |
| printf("Can only run test with all CPUs online\n"); |
| return true; |
| } |
| |
| uint runs = TEST_RUNS; |
| |
| /* Test that we're actually blocking and only signaling the ones we target */ |
| for (uint i = 0; i < runs; ++i) { |
| LTRACEF("Sequential test\n"); |
| int inorder_counter = 0; |
| for (uint i = 0; i < num_cpus; ++i) { |
| mp_sync_exec(MP_IPI_TARGET_MASK, 1u << i, inorder_count_task, &inorder_counter); |
| LTRACEF(" Finished signaling CPU %u\n", i); |
| } |
| } |
| |
| /* Test that we can signal multiple CPUs at the same time */ |
| for (uint i = 0; i < runs; ++i) { |
| LTRACEF("Counter test (%u CPUs)\n", num_cpus); |
| int counter = 0; |
| |
| spin_lock_saved_state_t irqstate; |
| arch_interrupt_save(&irqstate, SPIN_LOCK_FLAG_INTERRUPTS); |
| |
| mp_sync_exec(MP_IPI_TARGET_ALL_BUT_LOCAL, 0, counter_task, &counter); |
| |
| arch_interrupt_restore(irqstate, SPIN_LOCK_FLAG_INTERRUPTS); |
| |
| LTRACEF(" Finished signaling all but local (%d)\n", counter); |
| ASSERT((uint)counter == num_cpus - 1); |
| } |
| |
| for (uint i = 0; i < runs; ++i) { |
| LTRACEF("Deadlock test\n"); |
| deadlock_test(); |
| LTRACEF("Deadlock test passed\n"); |
| } |
| |
| END_TEST; |
| } |
| |
| UNITTEST_START_TESTCASE(sync_ipi_tests) |
| UNITTEST("sync_ipi_tests", sync_ipi_tests) |
| UNITTEST_END_TESTCASE(sync_ipi_tests, "sync_ipi_tests", "sync_ipi_tests"); |