1/*
2 * Copyright 2017, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(DATA61_BSD)
11 */
12
13#include <autoconf.h>
14#include <sel4test-driver/gen_config.h>
15
16#include "../helpers.h"
17
18static double fpu_calculation(void)
19{
20    double a = (double)3.141;
21    for (int i = 0; i < 10000; i++) {
22        a = a * 1.123 + (a / 3);
23        a /= 1.111;
24        while (a > 100.0) {
25            a /= 3.1234563212;
26        }
27        while (a < 2.0) {
28            a += 1.1232132131;
29        }
30    }
31
32    return a;
33}
34
35/*
36 * Ensure basic FPU functionality works.
37 *
38 * For processors without a FPU, this tests that maths libraries link
39 * correctly.
40 */
41static int test_fpu_trivial(env_t env)
42{
43    int i;
44    volatile double b;
45    double a = (double)3.141592653589793238462643383279;
46
47    for (i = 0; i < 100; i++) {
48        a = a * 3 + (a / 3);
49    }
50    b = a;
51    (void)b;
52    return sel4test_get_result();
53}
54DEFINE_TEST(FPU0000, "Ensure that simple FPU operations work", test_fpu_trivial, true)
55
56static int
57fpu_worker(seL4_Word p1, seL4_Word p2, seL4_Word p3, seL4_Word p4)
58{
59    volatile double *state = (volatile double *)p1;
60    int num_iterations = p2;
61    static volatile int preemption_counter = 0;
62    int num_preemptions = 0;
63
64    while (num_iterations >= 0) {
65        int counter_init = preemption_counter;
66
67        /* Do some random calculation (where we know the result). */
68        double a = fpu_calculation();
69
70        /* It's workaround to solve precision discrepancy when comparing
71         * floating value in FPU from different sources */
72        *state = a;
73        a = *state;
74
75        /* Determine if we were preempted mid-calculation. */
76        if (counter_init != preemption_counter) {
77            num_preemptions++;
78        }
79        preemption_counter++;
80
81        num_iterations--;
82    }
83
84    return num_preemptions;
85}
86
87/*
88 * Test that multiple threads using the FPU simulataneously can't corrupt each
89 * other.
90 *
91 * Early versions of seL4 had a bug here because we were not context-switching
92 * the FPU at all. Oops.
93 */
94static int test_fpu_multithreaded(struct env *env)
95{
96    const int NUM_THREADS = 4;
97    helper_thread_t thread[NUM_THREADS];
98    volatile double thread_state[NUM_THREADS];
99    seL4_Word iterations = 1;
100    int num_preemptions = 0;
101
102    /*
103     * We keep increasing the number of iterations ours users should calculate
104     * for until they notice themselves being preempted a few times.
105     */
106    do {
107        /* Start the threads running. */
108        for (int i = 0; i < NUM_THREADS; i++) {
109            create_helper_thread(env, &thread[i]);
110            set_helper_priority(env, &thread[i], 100);
111            start_helper(env, &thread[i], fpu_worker,
112                         (seL4_Word) &thread_state[i], iterations, 0, 0);
113        }
114
115        /* Wait for the threads to finish. */
116        num_preemptions = 0;
117        for (int i = 0; i < NUM_THREADS; i++) {
118            num_preemptions += wait_for_helper(&thread[i]);
119            cleanup_helper(env, &thread[i]);
120        }
121
122        /* Ensure they all got the same result. An assert failure here
123         * indicates FPU corrupt (i.e., a kernel bug). */
124        for (int i = 0; i < NUM_THREADS; i++) {
125            test_assert(thread_state[i] == thread_state[(i + 1) % NUM_THREADS]);
126        }
127
128        /* If we didn't get enough preemptions, restart everything again. */
129        iterations *= 2;
130    } while (num_preemptions < 20);
131
132    return sel4test_get_result();
133}
134DEFINE_TEST(FPU0001, "Ensure multiple threads can use FPU simultaneously", test_fpu_multithreaded,
135            !config_set(CONFIG_FT))
136
137static int
138smp_fpu_worker(volatile seL4_Word *ex, volatile seL4_Word *run)
139{
140    double a = 0;
141    while (*run) {
142        /* Do some random calculation where we know the result in 'ex'. */
143        a = fpu_calculation();
144
145        /* Values should always be the same.
146         * We use 'memcmp' to compare the results as otherwise this comparison could happen
147         * in FPU directly but 'a' is already in FPU and 'ex' is copied from register.
148         * This may result in different precision when comparing in FPU */
149        if (memcmp(&a, (seL4_Word *) ex, sizeof(double)) != 0) {
150            return 1;
151        }
152
153        a = 0;
154    }
155    return 0;
156}
157
158int smp_test_fpu(env_t env)
159{
160    volatile seL4_Word run = 1;
161    volatile double ex = fpu_calculation();
162    helper_thread_t t[env->cores];
163    ZF_LOGD("smp_test_fpu\n");
164
165    for (int i = 0; i < env->cores; i++) {
166        create_helper_thread(env, &t[i]);
167
168        set_helper_affinity(env, &t[i], i);
169        start_helper(env, &t[i], (helper_fn_t) smp_fpu_worker, (seL4_Word) &ex, (seL4_Word) &run, 0, 0);
170    }
171
172    /* Lets threads check in and do some calculation */
173    sel4test_sleep(env, 10 * NS_IN_MS);
174
175    /* Do lots of migrations */
176    for (int it = 0; it < 100; it++) {
177        for (int i = 0; i < env->cores; i++) {
178            /* Migrate threads to next core... */
179            set_helper_affinity(env, &t[i], (i + 1) % env->cores);
180        }
181        /* Lets do some calculation */
182        sel4test_sleep(env, 10 * NS_IN_MS);
183    }
184
185    /* Notify threads to return */
186    run = 0;
187
188    for (int i = 0; i < env->cores; i++) {
189        test_check(wait_for_helper(&t[i]) == 0);
190        cleanup_helper(env, &t[i]);
191    }
192
193    return sel4test_get_result();
194}
195DEFINE_TEST(FPU0002, "Test FPU remain valid across core migration", smp_test_fpu,
196            config_set(CONFIG_MAX_NUM_NODES) &&config_set(CONFIG_HAVE_TIMER) &&CONFIG_MAX_NUM_NODES > 1)
197