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