| // Copyright Microsoft and CHERIoT Contributors. |
| // SPDX-License-Identifier: MIT |
| |
| #define TEST_NAME "Futex" |
| #include "tests.hh" |
| #include <cheri.hh> |
| #include <cheriot-atomic.hh> |
| #include <errno.h> |
| #include <futex.h> |
| #include <interrupt.h> |
| #include <thread.h> |
| #include <thread_pool.h> |
| |
| using namespace CHERI; |
| using namespace thread_pool; |
| |
| #ifdef SAIL |
| DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(interruptCapability, |
| FakeInterrupt, |
| true, |
| true); |
| #endif |
| |
| int test_futex() |
| { |
| static uint32_t futex; |
| int ret; |
| // Make sure that waking a futex with no sleepers doesn't crash! |
| ret = futex_wake(&futex, 1); |
| TEST(ret == 0, "Waking a futex with no sleepers should return 0"); |
| // Wake in another thread, ensure that we don't wake until the futex value |
| // has been set to 1. |
| async([]() { |
| futex = 1; |
| futex_wake(&futex, 1); |
| }); |
| debug_log("Calling blocking futex_wait"); |
| ret = futex_wait(&futex, 0); |
| TEST(ret == 0, "futex_wait returned {}, expected 0", ret); |
| TEST(futex == 1, |
| "Thread should not have woken up until the futex had been set to 1"); |
| // Check that a futex wait with a mismatched value returns immediately. |
| debug_log("Calling futex_wait and expecting immediate return"); |
| ret = futex_wait(&futex, 0); |
| TEST(ret == 0, "futex_wait returned {}, expected 0", ret); |
| debug_log("Calling futex with timeout"); |
| { |
| Timeout t{3}; |
| auto err = futex_timed_wait(&t, &futex, 1); |
| TEST(err == -ETIMEDOUT, |
| "futex_timed_wait returned {}, expected {}", |
| err, |
| -ETIMEDOUT); |
| TEST(t.elapsed >= 3, |
| "futex_timed_wait timed out but elapsed ticks {} too small", |
| t.elapsed); |
| } |
| Timeout t{3}; |
| auto err = futex_timed_wait(&t, &futex, 0); |
| TEST(err == 0, "futex_timed_wait returned {}, expected {}", err, 0); |
| // Some tests for invalid API usage. |
| ret = futex_wait(nullptr, 0); |
| TEST(ret == -EINVAL, |
| "futex_wait returned {} when called with a null pointer, expected {}", |
| ret, |
| -EINVAL); |
| ret = futex_wake(nullptr, 0); |
| TEST(ret == -EINVAL, |
| "futex_wake returned {} when called with a null pointer, expected {}", |
| ret, |
| -EINVAL); |
| Capability fcap{&futex}; |
| fcap.permissions() &= PermissionSet::omnipotent().without(Permission::Load); |
| ret = futex_wait(fcap, 0); |
| TEST( |
| ret == -EINVAL, |
| "futex_wait returned {} when called without load permission, expected {}", |
| ret, |
| -EINVAL); |
| fcap = &futex; |
| fcap.permissions() &= |
| PermissionSet::omnipotent().without(Permission::Store); |
| ret = futex_wake(fcap, 1); |
| TEST(ret == -EINVAL, |
| "futex_wake returned {} when called without store permission, " |
| "expected {}", |
| ret, |
| -EINVAL); |
| |
| #ifdef SAIL |
| // If we're targeting Sail, also do some basic tests of the interrupt |
| // capability. We don't have an interrupt controller, so these tests are |
| // quite rudimentary. |
| Capability<const uint32_t> interruptFutex = |
| interrupt_futex_get(STATIC_SEALED_VALUE(interruptCapability)); |
| TEST(interruptFutex.is_valid(), |
| "Interrupt futex {} is not valid", |
| interruptFutex); |
| TEST(!interruptFutex.permissions().contains(Permission::Store), |
| "Interrupt futex {} should not have store permission", |
| interruptFutex); |
| TEST(*interruptFutex == 0, |
| "Interrupt futex {} for fake interrupt should not have fired but " |
| "shows {} interrupts", |
| interruptFutex, |
| *interruptFutex); |
| TEST(interrupt_complete(STATIC_SEALED_VALUE(interruptCapability)) == 0, |
| "interrupt_complete returned an error unexpectedly"); |
| TEST(interrupt_complete(nullptr) != 0, |
| "interrupt_complete returned success unexpectedly"); |
| #endif |
| |
| debug_log("Starting priority inheritance test"); |
| futex = 0; |
| |
| static cheriot::atomic<int> state; |
| auto priorityBug = []() { |
| if (thread_id_get() == 2) |
| { |
| debug_log("Medium-priority task starting"); |
| // We are the high priority thread, wait for the futex to be |
| // acquired and then spin and never yield. |
| while (state != 1) |
| { |
| Timeout t{3}; |
| thread_sleep(&t); |
| } |
| debug_log("Consuming all CPU on medium-priority thread"); |
| state = 2; |
| while (state != 4) {} |
| debug_log("Finishing medium-priority task"); |
| } |
| else |
| { |
| debug_log("Low-priority task starting"); |
| futex = thread_id_get(); |
| state = 1; |
| debug_log("Low-priority thread acquired futex, yielding"); |
| // Now the high priority thread should run. |
| while (state != 3) |
| { |
| yield(); |
| } |
| debug_log("Low-priority thread finished, unlocking"); |
| state = 4; |
| futex = 0; |
| futex_wake(&futex, 1); |
| } |
| }; |
| async(priorityBug); |
| async(priorityBug); |
| debug_log("Waiting for background threads to enter the right state"); |
| while (state != 2) |
| { |
| Timeout t{3}; |
| thread_sleep(&t); |
| } |
| debug_log("High-priority thread attempting to acquire futex owned by " |
| "low-priority thread without priority propagation"); |
| state = 3; |
| t.remaining = 1; |
| futex_timed_wait(&t, &futex, futex, FutexNone); |
| TEST(futex != 0, "Made progress surprisingly!"); |
| debug_log("High-priority thread attempting to acquire futex owned by " |
| "low-priority thread with priority propagation"); |
| t.remaining = 4; |
| futex_timed_wait(&t, &futex, futex, FutexPriorityInheritance); |
| TEST(futex == 0, "Failed to make progress!"); |
| |
| futex = 1234; |
| t.remaining = 1; |
| ret = futex_timed_wait(&t, &futex, futex, FutexPriorityInheritance); |
| TEST(ret == -EINVAL, |
| "PI futex with an invalid thread ID returned {}, should be {}", |
| ret, |
| -EINVAL); |
| |
| futex = 0; |
| debug_log( |
| "Testing priority inheriting futex_timed_wait with zero thread ID"); |
| // zero is not a valid thread ID for priority inheriting futex. Previously |
| // this caused a crash due to OOB access but better to return -EINVAL. |
| ret = futex_timed_wait(&t, &futex, 0, FutexPriorityInheritance); |
| TEST(ret == -EINVAL, |
| "PI futex with a zero thread ID returned {}, should be {}", |
| ret, |
| -EINVAL); |
| return 0; |
| } |