1// Copyright 2016 The Fuchsia Authors
2// Copyright (c) 2012 Travis Geiselbrecht
3//
4// Use of this source code is governed by a MIT-style
5// license that can be found in the LICENSE file or at
6// https://opensource.org/licenses/MIT
7
8#include "tests.h"
9
10#include <arch/ops.h>
11#include <assert.h>
12#include <err.h>
13#include <fbl/algorithm.h>
14#include <kernel/event.h>
15#include <kernel/mp.h>
16#include <kernel/thread.h>
17#include <lib/unittest/unittest.h>
18#include <stdio.h>
19#include <trace.h>
20#include <zircon/types.h>
21
22#define LOCAL_TRACE 0
23
24#define TEST_RUNS 1000
25
26static void inorder_count_task(void* raw_context) {
27    ASSERT(arch_ints_disabled());
28    ASSERT(arch_blocking_disallowed());
29    int* inorder_counter = (int*)raw_context;
30    uint cpu_num = arch_curr_cpu_num();
31
32    int oldval = atomic_add(inorder_counter, 1);
33    ASSERT(oldval == (int)cpu_num);
34    LTRACEF("  CPU %u checked in\n", cpu_num);
35}
36
37static void counter_task(void* raw_context) {
38    ASSERT(arch_ints_disabled());
39    ASSERT(arch_blocking_disallowed());
40    int* counter = (int*)raw_context;
41    atomic_add(counter, 1);
42}
43
44static int deadlock_test_thread(void* arg) {
45    event_t* gate = (event_t*)arg;
46    event_wait(gate);
47
48    int counter = 0;
49    arch_disable_ints();
50    mp_sync_exec(MP_IPI_TARGET_ALL_BUT_LOCAL, 0, counter_task, &counter);
51    arch_enable_ints();
52    return 0;
53}
54
55static void deadlock_test(void) {
56    /* Test for a deadlock caused by multiple CPUs broadcasting concurrently */
57
58    event_t gate = EVENT_INITIAL_VALUE(gate, false, 0);
59
60    thread_t* threads[5] = {0};
61    for (uint i = 0; i < fbl::count_of(threads); ++i) {
62        threads[i] = thread_create("sync_ipi_deadlock", deadlock_test_thread, &gate, DEFAULT_PRIORITY);
63        if (!threads[i]) {
64            TRACEF("  failed to create thread\n");
65            goto cleanup;
66        }
67        thread_resume(threads[i]);
68    }
69
70    event_signal(&gate, true);
71
72cleanup:
73    for (uint i = 0; i < fbl::count_of(threads); ++i) {
74        if (threads[i]) {
75            thread_join(threads[i], NULL, ZX_TIME_INFINITE);
76        }
77    }
78    event_destroy(&gate);
79};
80
81static bool sync_ipi_tests() {
82    BEGIN_TEST;
83
84    uint num_cpus = arch_max_num_cpus();
85    uint online = mp_get_online_mask();
86    if (online != (1U << num_cpus) - 1) {
87        printf("Can only run test with all CPUs online\n");
88        return true;
89    }
90
91    uint runs = TEST_RUNS;
92
93    /* Test that we're actually blocking and only signaling the ones we target */
94    for (uint i = 0; i < runs; ++i) {
95        LTRACEF("Sequential test\n");
96        int inorder_counter = 0;
97        for (uint i = 0; i < num_cpus; ++i) {
98            mp_sync_exec(MP_IPI_TARGET_MASK, 1u << i, inorder_count_task, &inorder_counter);
99            LTRACEF("  Finished signaling CPU %u\n", i);
100        }
101    }
102
103    /* Test that we can signal multiple CPUs at the same time */
104    for (uint i = 0; i < runs; ++i) {
105        LTRACEF("Counter test (%u CPUs)\n", num_cpus);
106        int counter = 0;
107
108        spin_lock_saved_state_t irqstate;
109        arch_interrupt_save(&irqstate, SPIN_LOCK_FLAG_INTERRUPTS);
110
111        mp_sync_exec(MP_IPI_TARGET_ALL_BUT_LOCAL, 0, counter_task, &counter);
112
113        arch_interrupt_restore(irqstate, SPIN_LOCK_FLAG_INTERRUPTS);
114
115        LTRACEF("  Finished signaling all but local (%d)\n", counter);
116        ASSERT((uint)counter == num_cpus - 1);
117    }
118
119    for (uint i = 0; i < runs; ++i) {
120        LTRACEF("Deadlock test\n");
121        deadlock_test();
122        LTRACEF("Deadlock test passed\n");
123    }
124
125    END_TEST;
126}
127
128UNITTEST_START_TESTCASE(sync_ipi_tests)
129UNITTEST("sync_ipi_tests", sync_ipi_tests)
130UNITTEST_END_TESTCASE(sync_ipi_tests, "sync_ipi_tests", "sync_ipi_tests");
131