blob: c33474a8dc4ca4fd8aa02303d51e29b7d03981b6 [file] [log] [blame]
#pragma once
#include <cdefs.h>
#include <stdatomic.h>
#include <stdint.h>
#include <thread.h>
#include <timeout.h>
/**
* State for a flag lock. Flag locks use a single futex word to store the lock
* state.
*/
struct FlagLockState
{
/**
* The lock word. One bit is used to indicate that the lock is held,
* another to indicate that the lock has waiters. In priority-inheriting
* locks, the low 16 bits store the ID of the thread that currently holds
* the lock.
*/
_Atomic(uint32_t) lockWord __if_cxx(= 0);
};
/**
* Ticket locks use two monotonic counters to store the lock state.
*/
struct TicketLockState
{
/**
* The value for the current ticket holder.
*/
_Atomic(uint32_t) current __if_cxx(= 0);
/**
* The value for the next ticket to be granted.
*/
_Atomic(uint32_t) next __if_cxx(= 0);
};
/**
* State for a recursive mutex. Recursive mutexes allow a single thread to
* acquire the lock multiple times.
*/
struct RecursiveMutexState
{
/**
* The underlying lock.
*/
struct FlagLockState lock;
/**
* The count of the times the lock has been acquired by the same thread.
* This must be initialised to 0.
*/
uint16_t depth __if_cxx(= 0);
};
/**
* State for a counting semaphore.
*/
struct CountingSemaphoreState
{
/**
* The current counter value.
*/
_Atomic(uint32_t) count;
/**
* The maximum value for the counter.
*/
uint32_t maxCount;
};
__BEGIN_DECLS
/**
* Try to lock a flag lock. This is the non-priority-inheriting version,
* sometimes called a binary semaphore on other platforms.
*
* Returns 0 on success, -ETIMEDOUT if the timeout expired, -EINVAL if the
* arguments are invalid, or -ENOENT if the lock is set in destruction mode.
*/
int __cheri_libcall flaglock_trylock(Timeout *timeout,
struct FlagLockState *lock);
/**
* Try to lock a flag lock. This is the priority-inheriting version. Some
* other platforms refer to this as a priority-inheriting mutex or simply a
* mutex.
*
* A higher-priority thread that calls this function while a lock is held will
* lend its priority to the thread that successfully called this function until
* that thread either releases the lock with `flaglock_unlock` or the timeout
* expires.
*
* Returns 0 on success, -ETIMEDOUT if the timeout expired, -EINVAL if the
* arguments are invalid, or -ENOENT if the lock is set in destruction mode.
*
* Note: if the object is deallocated while trying to acquire the lock, then
* this will fault. In many cases, this is called at a compartment boundary
* and so this is fine. If it is not acceptable, use `heap_claim_fast` to
* ensure that the object remains live until after the call.
*/
int __cheri_libcall
flaglock_priority_inheriting_trylock(Timeout *timeout,
struct FlagLockState *lock);
/**
* Convenience wrapper to acquire a lock with an unlimited timeout. See
* `flaglock_trylock` for more details.
*/
__always_inline static inline void flaglock_lock(struct FlagLockState *lock)
{
Timeout t = {0, UnlimitedTimeout};
flaglock_trylock(&t, lock);
}
/**
* Convenience wrapper to acquire a lock with an unlimited timeout. See
* `flaglock_priority_inheriting_trylock` for more details.
*/
__always_inline static inline void
flaglock_priority_inheriting_lock(struct FlagLockState *lock)
{
Timeout t = {0, UnlimitedTimeout};
flaglock_priority_inheriting_trylock(&t, lock);
}
/**
* Unlock a flag lock. This can be called with either form of flag lock.
*/
void __cheri_libcall flaglock_unlock(struct FlagLockState *lock);
/**
* Set a flag lock in destruction mode.
*
* Locks in destruction mode cannot be acquired by other threads. Any threads
* currently attempting to acquire the lock will wake and fail to acquire the
* lock. This should be called before deallocating an object that contains a
* lock.
*
* Note that callers do not need to hold the lock; the ability to upgrade for
* destruction without holding the lock is useful for cleaning up from the
* error handler.
*/
void __cheri_libcall
flaglock_upgrade_for_destruction(struct FlagLockState *lock);
/**
* 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.
*
* The return value is 0 if the lock is not owned or if called on a
* non-priority inherited flag lock. The return value is undefined if called on
* an uninitialized lock.
*
* This *will* race with succesful `lock` and `unlock` operations on other
* threads, and should thus not be used to check if the lock is owned.
*
* The main use case for this function is in the error handler to check whether
* or not the lock is owned by the thread on which the error handler was
* invoked. In this case we can call this function and compare the result with
* `thread_id_get` to know if the current thread owns the lock.
*/
__always_inline static inline uint16_t
flaglock_priority_inheriting_get_owner_thread_id(struct FlagLockState *lock)
{
// The lock must be held at this point for the value to be stable so do
// a non-atomic read (simply &ing the lock word would result in a
// libcall for the atomic operation).
return ((*(uint32_t *)&(lock->lockWord)) & 0x0000ffff);
}
/**
* Try to acquire a recursive mutex. This is a priority-inheriting mutex that
* can be acquired multiple times by the same thread.
*
* A higher-priority thread that calls this function while a lock is held will
* lend its priority to the thread that successfully called this function until
* that thread either releases the lock with `flaglock_unlock` or the timeout
* expires.
*
* Returns 0 on success, -ETIMEDOUT if the timeout expired, or -EINVAL if the
* arguments are invalid. Can also return -EOVERFLOW if the lock depth would
* overflow the depth counter.
*/
int __cheri_libcall recursivemutex_trylock(Timeout *timeout,
struct RecursiveMutexState *lock);
/**
* Unlock a recursive mutex. Note: This does not check that the current thread
* holds the lock. An error-checking mutex can be implemented by wrapping this
* in a check that the `owner` field matches the current thread ID.
*
* Returns 0 on success. Succeeds unconditionally (future versions may return
* non-zero on error).
*/
int __cheri_libcall recursivemutex_unlock(struct RecursiveMutexState *mutex);
/**
* Acquire a ticket lock. Ticket locks, by design, cannot support a try-lock
* operation and so will block forever until the lock is acquired.
*/
void __cheri_libcall ticketlock_lock(struct TicketLockState *lock);
/**
* Release a ticket lock.
*/
void __cheri_libcall ticketlock_unlock(struct TicketLockState *lock);
/**
* Semaphore get operation, decrements the semaphore count. Returns 0 on
* success, -ETIMEDOUT if the timeout expired. Can also return -EINVAL if the
* arguments are invalid.
*/
int __cheri_libcall semaphore_get(Timeout *timeout,
struct CountingSemaphoreState *semaphore);
/**
* Semaphore put operation. Returns 0 on success, -EINVAL if this would push
* the semaphore count above the maximum.
*/
int __cheri_libcall semaphore_put(struct CountingSemaphoreState *semaphore);
__END_DECLS