| #pragma once | |
| #include <cdefs.h> | |
| #include <cheriot-atomic.hh> | |
| #include <debug.hh> | |
| #include <errno.h> | |
| #include <futex.h> | |
| #include <locks.h> | |
| #include <thread.h> | |
| static constexpr bool DebugLocks = | |
| #ifdef DEBUG_LOCKS | |
| DEBUG_LOCKS | |
| #else | |
| false | |
| #endif | |
| ; | |
| using LockDebug = ConditionalDebug<DebugLocks, "Locking">; | |
| __clang_ignored_warning_push("-Watomic-alignment"); | |
| /** | |
| * A simple flag log, wrapping an atomic word used with the `futex` calls. | |
| * Threads blocked on this will be woken in priority order. If | |
| * `IsPriorityInherited` is set, priority is inherited by waiters to avoid | |
| * priority inversion issues. | |
| * | |
| * The lock word that this wraps is directly accessibly by any malicious | |
| * compartment that has a reference to this thread. If this is a security | |
| * concern then you may have other problems: a malicious compartment with | |
| * access to a mutex's interface (irrespective of the underlying | |
| * implementation) can cause deadlock by spuriously acquiring a lock or cause | |
| * data corruption via races by spuriously releasing it. Anything that | |
| * requires mutual exclusion in the presence of mutual distrust should | |
| * consider an using a lock manager compartment with an API that returns a | |
| * single-use capability to unlock on any lock call. | |
| */ | |
| template<bool IsPriorityInherited> | |
| class FlagLockGeneric | |
| { | |
| FlagLockState state; | |
| public: | |
| /** | |
| * Attempt to acquire the lock, blocking until a timeout specified by the | |
| * `timeout` parameter has expired. | |
| */ | |
| __always_inline bool try_lock(Timeout *timeout) | |
| { | |
| if constexpr (IsPriorityInherited) | |
| { | |
| return flaglock_priority_inheriting_trylock(timeout, &state) == 0; | |
| } | |
| else | |
| { | |
| return flaglock_trylock(timeout, &state) == 0; | |
| } | |
| } | |
| /** | |
| * Try to acquire the lock, do not block. | |
| */ | |
| __always_inline bool try_lock() | |
| { | |
| Timeout t{0}; | |
| return try_lock(&t); | |
| } | |
| /** | |
| * Acquire the lock, potentially blocking forever. | |
| */ | |
| __always_inline void lock() | |
| { | |
| Timeout t{UnlimitedTimeout}; | |
| try_lock(&t); | |
| } | |
| /** | |
| * Release the lock. | |
| * | |
| * Note: This does not check that the lock is owned by the calling thread. | |
| */ | |
| __always_inline void unlock() | |
| { | |
| flaglock_unlock(&state); | |
| } | |
| /** | |
| * Set the lock in destruction mode. See the documentation of | |
| * `flaglock_upgrade_for_destruction` for more information. | |
| */ | |
| __always_inline void upgrade_for_destruction() | |
| { | |
| flaglock_upgrade_for_destruction(&state); | |
| } | |
| /** | |
| * Return the thread ID of the owner of the lock. | |
| * | |
| * This is only available for priority inherited locks, as this is the | |
| * only case where we store the thread ID of the owner. See the | |
| * documentation of `flaglock_priority_inheriting_get_owner_thread_id` | |
| * for more information. | |
| */ | |
| __always_inline uint16_t get_owner_thread_id() requires(IsPriorityInherited) | |
| { | |
| return flaglock_priority_inheriting_get_owner_thread_id(&state); | |
| } | |
| }; | |
| /** | |
| * Priority-inheriting recursive mutex. This can be acquired multiple times | |
| * from the same thread. | |
| */ | |
| class RecursiveMutex | |
| { | |
| /// State for the underling recursive mutex. | |
| RecursiveMutexState state; | |
| public: | |
| /** | |
| * Attempt to acquire the lock, blocking until a timeout specified by the | |
| * `timeout` parameter has expired. | |
| */ | |
| __always_inline bool try_lock(Timeout *timeout) | |
| { | |
| return recursivemutex_trylock(timeout, &state) == 0; | |
| } | |
| /** | |
| * Try to acquire the lock, do not block. | |
| */ | |
| __always_inline bool try_lock() | |
| { | |
| Timeout t{0}; | |
| return try_lock(&t); | |
| } | |
| /** | |
| * Acquire the lock, potentially blocking forever. | |
| */ | |
| __always_inline void lock() | |
| { | |
| Timeout t{UnlimitedTimeout}; | |
| try_lock(&t); | |
| } | |
| /** | |
| * Release the lock. | |
| * | |
| * Note: This does not check that the lock is owned by the calling thread. | |
| */ | |
| __always_inline void unlock() | |
| { | |
| recursivemutex_unlock(&state); | |
| } | |
| /** | |
| * Set the lock in destruction mode. See the documentation of | |
| * `flaglock_upgrade_for_destruction` for more information. | |
| */ | |
| __always_inline void upgrade_for_destruction() | |
| { | |
| flaglock_upgrade_for_destruction(&state.lock); | |
| } | |
| }; | |
| /** | |
| * A simple ticket lock. | |
| * | |
| * A ticket lock ensures that threads that arrive are serviced in order, | |
| * without regard for priorities. It has no mechanism for tracking tickets | |
| * that are discarded and so does not implement a `try_lock` API. | |
| */ | |
| class TicketLock | |
| { | |
| TicketLockState state; | |
| public: | |
| /** | |
| * Acquire the lock. | |
| */ | |
| __always_inline void lock() | |
| { | |
| ticketlock_lock(&state); | |
| } | |
| /** | |
| * Release the lock. | |
| * | |
| * Note: This does not check that the lock is owned by the calling thread. | |
| */ | |
| __always_inline void unlock() | |
| { | |
| ticketlock_unlock(&state); | |
| } | |
| }; | |
| /** | |
| * Class that implements the locking concept but does not perform locking. | |
| * This is intended to be used with templated data structures that support | |
| * locking, for instantiations that do not require locking. | |
| */ | |
| class NoLock | |
| { | |
| public: | |
| /** | |
| * Attempt to acquire the lock with a timeout. Always succeeds. | |
| */ | |
| bool try_lock(Timeout *timeout) | |
| { | |
| return true; | |
| } | |
| /** | |
| * Try to acquire the lock, do not block. Always succeeds. | |
| */ | |
| bool try_lock() | |
| { | |
| return true; | |
| } | |
| /** | |
| * Acquire the lock. Always succeeds | |
| */ | |
| void lock() {} | |
| /** | |
| * Release the lock. Does nothing. | |
| */ | |
| void unlock() {} | |
| }; | |
| using FlagLock = FlagLockGeneric<false>; | |
| using FlagLockPriorityInherited = FlagLockGeneric<true>; | |
| template<typename T> | |
| concept Lockable = requires(T l) | |
| { | |
| {l.lock()}; | |
| {l.unlock()}; | |
| }; | |
| template<typename T> | |
| concept TryLockable = Lockable<T> && requires(T l, Timeout *t) | |
| { | |
| { | |
| l.try_lock(t) | |
| } -> std::same_as<bool>; | |
| }; | |
| static_assert(TryLockable<NoLock>); | |
| static_assert(TryLockable<FlagLock>); | |
| static_assert(TryLockable<FlagLockPriorityInherited>); | |
| static_assert(Lockable<TicketLock>); | |
| /** | |
| * A simple RAII type that owns a lock. | |
| */ | |
| template<typename Lock> | |
| class LockGuard | |
| { | |
| /// A reference to the managed lock | |
| Lock *wrappedLock; | |
| /// Flag indicating whether the lock is owned. | |
| bool isOwned; | |
| public: | |
| /// Constructor, acquires the lock. | |
| [[nodiscard]] explicit LockGuard(Lock &lock) | |
| : wrappedLock(&lock), isOwned(true) | |
| { | |
| wrappedLock->lock(); | |
| } | |
| /// Constructor, attempts to acquire the lock with a timeout. | |
| [[nodiscard]] explicit LockGuard(Lock &lock, Timeout *timeout) requires( | |
| TryLockable<Lock>) | |
| : wrappedLock(&lock), isOwned(false) | |
| { | |
| try_lock(timeout); | |
| } | |
| /// Move constructor, transfers ownership of the lock. | |
| [[nodiscard]] explicit LockGuard(LockGuard &&guard) | |
| : wrappedLock(guard.wrappedLock), isOwned(guard.isOwned) | |
| { | |
| guard.wrappedLock = nullptr; | |
| guard.isOwned = false; | |
| } | |
| /** | |
| * Explicitly lock the wrapped lock. Must be called with the lock unlocked. | |
| */ | |
| void lock() | |
| { | |
| LockDebug::Assert(!isOwned, "Trying to lock an already-locked lock"); | |
| wrappedLock->lock(); | |
| isOwned = true; | |
| } | |
| /** | |
| * Explicitly lock the wrapped lock. Must be called with the lock locked by | |
| * this wrapper. | |
| */ | |
| void unlock() | |
| { | |
| LockDebug::Assert(isOwned, "Trying to unlock an unlocked lock"); | |
| wrappedLock->unlock(); | |
| isOwned = false; | |
| } | |
| /** | |
| * Unwrap the lock without unlocking it. This is useful to call before | |
| * destroying a lock. | |
| */ | |
| void release() | |
| { | |
| wrappedLock = nullptr; | |
| isOwned = false; | |
| } | |
| /** | |
| * If the underlying lock type supports locking with a timeout, try to lock | |
| * it with the specified timeout. This must be called with the lock | |
| * unlocked. Returns true if the lock has been acquired, false otherwise. | |
| */ | |
| bool try_lock(Timeout *timeout) requires(TryLockable<Lock>) | |
| { | |
| LockDebug::Assert(!isOwned, "Trying to lock an already-locked lock"); | |
| isOwned = wrappedLock->try_lock(timeout); | |
| return isOwned; | |
| } | |
| /// Destructor, releases the lock. | |
| ~LockGuard() | |
| { | |
| if (isOwned) | |
| { | |
| wrappedLock->unlock(); | |
| } | |
| } | |
| /** | |
| * Conversion to bool. Returns true if this guard owns the lock, false | |
| * otherwise. This allows lock guards to be used with a timeout in | |
| * conditional blocks, such as: | |
| * | |
| * ``` | |
| * if (LockGuard g{lock, timeout}) | |
| * { | |
| * // Run this code if we acquired the lock, releasing the lock at the | |
| * end. | |
| * } | |
| * else | |
| * { | |
| * // Run this code if we did not acquire the lock. | |
| * } | |
| * ``` | |
| */ | |
| operator bool() | |
| { | |
| return isOwned; | |
| } | |
| }; | |
| __clang_ignored_warning_pop(); |