| /* |
| * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) |
| * |
| * SPDX-License-Identifier: BSD-2-Clause |
| */ |
| |
| #include <autoconf.h> |
| #include <sel4test-driver/gen_config.h> |
| |
| #include "../helpers.h" |
| |
| static double fpu_calculation(void) |
| { |
| double a = (double)3.141; |
| for (int i = 0; i < 10000; i++) { |
| a = a * 1.123 + (a / 3); |
| a /= 1.111; |
| while (a > 100.0) { |
| a /= 3.1234563212; |
| } |
| while (a < 2.0) { |
| a += 1.1232132131; |
| } |
| } |
| |
| return a; |
| } |
| |
| /* |
| * Ensure basic FPU functionality works. |
| * |
| * For processors without a FPU, this tests that maths libraries link |
| * correctly. |
| */ |
| static int test_fpu_trivial(env_t env) |
| { |
| int i; |
| volatile double b; |
| double a = (double)3.141592653589793238462643383279; |
| |
| for (i = 0; i < 100; i++) { |
| a = a * 3 + (a / 3); |
| } |
| b = a; |
| (void)b; |
| return sel4test_get_result(); |
| } |
| DEFINE_TEST(FPU0000, "Ensure that simple FPU operations work", test_fpu_trivial, true) |
| |
| static int |
| fpu_worker(seL4_Word p1, seL4_Word p2, seL4_Word p3, seL4_Word p4) |
| { |
| volatile double *state = (volatile double *)p1; |
| int num_iterations = p2; |
| static volatile int preemption_counter = 0; |
| int num_preemptions = 0; |
| |
| while (num_iterations >= 0) { |
| int counter_init = preemption_counter; |
| |
| /* Do some random calculation (where we know the result). */ |
| double a = fpu_calculation(); |
| |
| /* It's workaround to solve precision discrepancy when comparing |
| * floating value in FPU from different sources */ |
| *state = a; |
| a = *state; |
| |
| /* Determine if we were preempted mid-calculation. */ |
| if (counter_init != preemption_counter) { |
| num_preemptions++; |
| } |
| preemption_counter++; |
| |
| num_iterations--; |
| } |
| |
| return num_preemptions; |
| } |
| |
| /* |
| * Test that multiple threads using the FPU simulataneously can't corrupt each |
| * other. |
| * |
| * Early versions of seL4 had a bug here because we were not context-switching |
| * the FPU at all. Oops. |
| */ |
| static int test_fpu_multithreaded(struct env *env) |
| { |
| const int NUM_THREADS = 4; |
| helper_thread_t thread[NUM_THREADS]; |
| volatile double thread_state[NUM_THREADS]; |
| seL4_Word iterations = 1; |
| int num_preemptions = 0; |
| |
| /* |
| * We keep increasing the number of iterations ours users should calculate |
| * for until they notice themselves being preempted a few times. |
| */ |
| do { |
| /* Start the threads running. */ |
| for (int i = 0; i < NUM_THREADS; i++) { |
| create_helper_thread(env, &thread[i]); |
| set_helper_priority(env, &thread[i], 100); |
| start_helper(env, &thread[i], fpu_worker, |
| (seL4_Word) &thread_state[i], iterations, 0, 0); |
| } |
| |
| /* Wait for the threads to finish. */ |
| num_preemptions = 0; |
| for (int i = 0; i < NUM_THREADS; i++) { |
| num_preemptions += wait_for_helper(&thread[i]); |
| cleanup_helper(env, &thread[i]); |
| } |
| |
| /* Ensure they all got the same result. An assert failure here |
| * indicates FPU corrupt (i.e., a kernel bug). */ |
| for (int i = 0; i < NUM_THREADS; i++) { |
| test_assert(thread_state[i] == thread_state[(i + 1) % NUM_THREADS]); |
| } |
| |
| /* If we didn't get enough preemptions, restart everything again. */ |
| iterations *= 2; |
| } while (num_preemptions < 20); |
| |
| return sel4test_get_result(); |
| } |
| DEFINE_TEST(FPU0001, "Ensure multiple threads can use FPU simultaneously", test_fpu_multithreaded, |
| !config_set(CONFIG_FT)) |
| |
| static int |
| smp_fpu_worker(volatile seL4_Word *ex, volatile seL4_Word *run) |
| { |
| double a = 0; |
| while (*run) { |
| /* Do some random calculation where we know the result in 'ex'. */ |
| a = fpu_calculation(); |
| |
| /* Values should always be the same. |
| * We use 'memcmp' to compare the results as otherwise this comparison could happen |
| * in FPU directly but 'a' is already in FPU and 'ex' is copied from register. |
| * This may result in different precision when comparing in FPU */ |
| if (memcmp(&a, (seL4_Word *) ex, sizeof(double)) != 0) { |
| return 1; |
| } |
| |
| a = 0; |
| } |
| return 0; |
| } |
| |
| int smp_test_fpu(env_t env) |
| { |
| volatile seL4_Word run = 1; |
| volatile double ex = fpu_calculation(); |
| helper_thread_t t[env->cores]; |
| ZF_LOGD("smp_test_fpu\n"); |
| |
| for (int i = 0; i < env->cores; i++) { |
| create_helper_thread(env, &t[i]); |
| |
| set_helper_affinity(env, &t[i], i); |
| start_helper(env, &t[i], (helper_fn_t) smp_fpu_worker, (seL4_Word) &ex, (seL4_Word) &run, 0, 0); |
| } |
| |
| /* Lets threads check in and do some calculation */ |
| sel4test_sleep(env, 10 * NS_IN_MS); |
| |
| /* Do lots of migrations */ |
| for (int it = 0; it < 100; it++) { |
| for (int i = 0; i < env->cores; i++) { |
| /* Migrate threads to next core... */ |
| set_helper_affinity(env, &t[i], (i + 1) % env->cores); |
| } |
| /* Lets do some calculation */ |
| sel4test_sleep(env, 10 * NS_IN_MS); |
| } |
| |
| /* Notify threads to return */ |
| run = 0; |
| |
| for (int i = 0; i < env->cores; i++) { |
| test_check(wait_for_helper(&t[i]) == 0); |
| cleanup_helper(env, &t[i]); |
| } |
| |
| return sel4test_get_result(); |
| } |
| DEFINE_TEST(FPU0002, "Test FPU remain valid across core migration", smp_test_fpu, |
| config_set(CONFIG_MAX_NUM_NODES) &&config_set(CONFIG_HAVE_TIMER) &&CONFIG_MAX_NUM_NODES > 1) |