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