blob: 6819f841337fa4937e88e43472b228c5cf067554 [file] [log] [blame]
#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();