|  | #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(); |