CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
Loading...
Searching...
No Matches
locks.h
Go to the documentation of this file.
1#pragma once
2#include <cdefs.h>
3#include <stdatomic.h>
4#include <stdint.h>
5#include <thread.h>
6#include <timeout.h>
7
8/**
9 * \file
10 * \brief C compatible interface to synchronisation primitives.
11 */
12
13/**
14 * State for a flag lock. Flag locks use a single futex word to store the lock
15 * state.
16 */
18{
19 /**
20 * The lock word. One bit is used to indicate that the lock is held,
21 * another to indicate that the lock has waiters. In priority-inheriting
22 * locks, the low 16 bits store the ID of the thread that currently holds
23 * the lock.
24 */
25 _Atomic(uint32_t) lockWord __if_cxx(= 0);
26};
27
28/**
29 * Ticket locks use two monotonic counters to store the lock state.
30 */
32{
33 /**
34 * The value for the current ticket holder.
35 */
36 _Atomic(uint32_t) current __if_cxx(= 0);
37 /**
38 * The value for the next ticket to be granted.
39 */
40 _Atomic(uint32_t) next __if_cxx(= 0);
41};
42
43/**
44 * State for a recursive mutex. Recursive mutexes allow a single thread to
45 * acquire the lock multiple times.
46 */
48{
49 /**
50 * The underlying lock.
51 */
53 /**
54 * The count of the times the lock has been acquired by the same thread.
55 * This must be initialised to 0.
56 */
57 uint16_t depth __if_cxx(= 0);
58};
59
60/**
61 * State for a counting semaphore.
62 */
64{
65 /**
66 * The current counter value.
67 */
68 _Atomic(uint32_t) count;
69
70 /**
71 * The maximum value for the counter.
72 */
73 uint32_t maxCount;
74};
75
76/**
77 * State for a barrier.
78 *
79 * A barrier is a primitive that allows a set of threads to rendezvous. Each
80 * thread arrives at the barrier and blocks. All threads can proceed once
81 * every thread is there. Barriers must be initialised with the number of
82 * threads that can reach them.
83 */
85{
86 /**
87 * The number of threads that have to rendezvous with this barrier to allow
88 * waiters to wake.
89 */
90 _Atomic(uint32_t) remaining;
91};
92
93/**
94 * Condition variable state.
95 */
97{
98 /**
99 * Sequence counter used to wait and notify waiters.
100 */
101 _Atomic(uint32_t) sequenceCounter;
102};
103
104/**
105 * State-machine states for run-once functions.
106 */
108{
109 /**
110 * The function has not run yet.
111 */
113 /**
114 * The function has started running but has not yet finished.
115 */
117 /**
118 * The function has run to completion.
119 */
121};
122
123/**
124 * Synchronisation state for run-once functions.
125 *
126 * This must be initialised to 0 (`OnceStateNotRun`) in C code. This happens
127 * automatically for globals and almost all correct uses of this type are in
128 * globals.
129 */
131{
132 _Atomic(enum OnceStateMachineStates)
134};
135
136__BEGIN_DECLS
137
138/**
139 * Try to lock a flag lock. This is the non-priority-inheriting version,
140 * sometimes called a binary semaphore on other platforms.
141 *
142 * Returns 0 on success, -ETIMEDOUT if the timeout expired, -EINVAL if the
143 * arguments are invalid, or -ENOENT if the lock is set in destruction mode.
144 */
145int __cheri_libcall flaglock_trylock(TimeoutArgument timeout,
146 struct FlagLockState *lock);
147
148/**
149 * Try to lock a flag lock. This is the priority-inheriting version. Some
150 * other platforms refer to this as a priority-inheriting mutex or simply a
151 * mutex.
152 *
153 * A higher-priority thread that calls this function while a lock is held will
154 * lend its priority to the thread that successfully called this function until
155 * that thread either releases the lock with `flaglock_unlock` or the timeout
156 * expires.
157 *
158 * Returns 0 on success, -ETIMEDOUT if the timeout expired, -EINVAL if the
159 * arguments are invalid, or -ENOENT if the lock is set in destruction mode.
160 *
161 * Note: if the object is deallocated while trying to acquire the lock, then
162 * this will fault. In many cases, this is called at a compartment boundary
163 * and so this is fine. If it is not acceptable, use `heap_claim_ephemeral` to
164 * ensure that the object remains live until after the call.
165 */
166int __cheri_libcall
168 struct FlagLockState *lock);
169
170/**
171 * Convenience wrapper to acquire a lock with an unlimited timeout. See
172 * `flaglock_trylock` for more details.
173 */
174__always_inline static inline void flaglock_lock(struct FlagLockState *lock)
175{
176 Timeout t = {0, UnlimitedTimeout};
177 flaglock_trylock(&t, lock);
178}
179
180/**
181 * Convenience wrapper to acquire a lock with an unlimited timeout. See
182 * `flaglock_priority_inheriting_trylock` for more details.
183 */
184__always_inline static inline void
190
191/**
192 * Unlock a flag lock. This can be called with either form of flag lock.
193 */
194void __cheri_libcall flaglock_unlock(struct FlagLockState *lock);
195
196/**
197 * Set a flag lock in destruction mode.
198 *
199 * Locks in destruction mode cannot be acquired by other threads. Any threads
200 * currently attempting to acquire the lock will wake and fail to acquire the
201 * lock. This should be called before deallocating an object that contains a
202 * lock.
203 *
204 * Note that callers do not need to hold the lock; the ability to upgrade for
205 * destruction without holding the lock is useful for cleaning up from the
206 * error handler.
207 */
208void __cheri_libcall
210
211/**
212 * Return the thread ID of the owner of the lock.
213 *
214 * This is only available for priority inherited locks, as this is the only
215 * case where we store the thread ID of the owner.
216 *
217 * The return value is 0 if the lock is not owned or if called on a
218 * non-priority inherited flag lock. The return value is undefined if called on
219 * an uninitialized lock.
220 *
221 * This *will* race with succesful `lock` and `unlock` operations on other
222 * threads, and should thus not be used to check if the lock is owned.
223 *
224 * The main use case for this function is in the error handler to check whether
225 * or not the lock is owned by the thread on which the error handler was
226 * invoked. In this case we can call this function and compare the result with
227 * `thread_id_get` to know if the current thread owns the lock.
228 */
229__always_inline static inline uint16_t
231{
232 // The lock must be held at this point for the value to be stable so do
233 // a non-atomic read (simply &ing the lock word would result in a
234 // libcall for the atomic operation).
235 return ((*(uint32_t *)&(lock->lockWord)) & 0x0000ffff);
236}
237
238/**
239 * Try to acquire a recursive mutex. This is a priority-inheriting mutex that
240 * can be acquired multiple times by the same thread.
241 *
242 * A higher-priority thread that calls this function while a lock is held will
243 * lend its priority to the thread that successfully called this function until
244 * that thread either releases the lock with `flaglock_unlock` or the timeout
245 * expires.
246 *
247 * Returns 0 on success, -ETIMEDOUT if the timeout expired, or -EINVAL if the
248 * arguments are invalid. Can also return -EOVERFLOW if the lock depth would
249 * overflow the depth counter.
250 */
251int __cheri_libcall recursivemutex_trylock(TimeoutArgument timeout,
252 struct RecursiveMutexState *lock);
253
254/**
255 * Unlock a recursive mutex. Note: This does not check that the current thread
256 * holds the lock. An error-checking mutex can be implemented by wrapping this
257 * in a check that the `owner` field matches the current thread ID.
258 *
259 * Returns 0 on success. Succeeds unconditionally (future versions may return
260 * non-zero on error).
261 */
262int __cheri_libcall recursivemutex_unlock(struct RecursiveMutexState *mutex);
263
264/**
265 * Acquire a ticket lock. Ticket locks, by design, cannot support a try-lock
266 * operation and so will block forever until the lock is acquired.
267 */
268void __cheri_libcall ticketlock_lock(struct TicketLockState *lock);
269
270/**
271 * Release a ticket lock.
272 */
273void __cheri_libcall ticketlock_unlock(struct TicketLockState *lock);
274
275/**
276 * Semaphore get operation, decrements the semaphore count. Returns 0 on
277 * success, -ETIMEDOUT if the timeout expired. Can also return -EINVAL if the
278 * arguments are invalid.
279 */
280int __cheri_libcall semaphore_get(TimeoutArgument timeout,
281 struct CountingSemaphoreState *semaphore);
282/**
283 * Semaphore put operation. Returns 0 on success, -EINVAL if this would push
284 * the semaphore count above the maximum.
285 */
286int __cheri_libcall semaphore_put(struct CountingSemaphoreState *semaphore);
287
288/**
289 * Wait on a barrier. Returns 0 or 1 on success, or `-ETIMEDOUT` on failure.
290 *
291 * - 0 indicates that this was the last thread to reach the barrier.
292 * - 1 indicates that this was a thread that blocked on the barrier and has now
293 * woken.
294 * - `-ETIMEDOUT` indicates that not all threads reached the barrier prior to
295 * the timeout.
296 *
297 * Other errors may be propagated from `futex_timed_wait` if they fail.
298 */
299int __cheriot_libcall barrier_timed_wait(TimeoutArgument timeout,
300 struct BarrierState *barrier);
301
302/**
303 * Helper that calls `barrier_timed_wait` with an unlimited timeout.
304 */
305static int barrier_wait(struct BarrierState *barrier)
306{
307 Timeout t = {0, UnlimitedTimeout};
308 return barrier_timed_wait(&t, barrier);
309}
310
311/**
312 * Signal the condition variable and wake up to the number of waiters specified
313 * by the second argument.
314 *
315 * Returns 0 on success. Returns the same error codes on failure as
316 * `futex_wake`. This function can fail only if you do not have enough stack
317 * or trusted stack to call `futex_wake`.
318 */
319int __cheriot_libcall
321 uint32_t waiters);
322
323/**
324 * Wait on a condition variable. This operation releases a mutex and
325 * atomically waits for the condition variable to be signalled.
326 *
327 * The mutex is provided via the `mutex` parameter and the operations to lock
328 * and unlock it are passed via the `mutexLock` and `mutexUnlock` parameters.
329 * These must return 0 on success. Any non-zero result will be propagated to
330 * the caller. The `mutexUnlock` function does not take a timeout and so
331 * should not return `-ETIMEDOUT`.
332 *
333 * Returns 0 on success, and either `-ETIMEDOUT` or an error forwarded from one
334 * of the callbacks on failure.
335 *
336 * Note that, unlike the POSIX equivalent, this does *not* reacquire the
337 * condition variable if this operation times out. This can be wrapped to
338 * produce POSIX-compatible behaviour by calling the mutex-wait function with
339 * an unlimited timeout if necessary, however the POSIX behaviour cannot be
340 * wrapped to provide a variant that does not expire past deadlines.
341 *
342 * This implementation mirrors the algorithm used in Bionic (Android) and has
343 * the bug that, if another thread calls `condition_variable_notify` *exactly*
344 * 2^32 times in between this implementation releasing the lock and sleeping,
345 * and then no thread subsequently signals the condition variable, then the
346 * thread calling `condition_variable_wait` will sleep forever.
347 */
348int __cheriot_libcall
350 struct ConditionVariableState *conditionVariable,
351 void *mutex,
352 int (*mutexLock)(TimeoutArgument, void *),
353 int (*mutexUnlock)(void *));
354
355__always_inline static inline int run_once(struct OnceState *state,
356 void (*callback)(__if_c(void)))
357{
358 /**
359 * Helper function that implements the slow path for `run_once`. This
360 * should not be called explicitly and is prototyped locally to avoid
361 * accidentally exposing it elsewhere.
362 */
363 int __cheriot_libcall run_once_slow_path(struct OnceState * state,
364 void (*callback)(__if_c(void)));
365 // Fast path, if intitialisation has run then we can elide the call with a
366 // single load and compare.
367 if (atomic_load_explicit(
368 &state->state, __if_cxx(std::) memory_order_relaxed) == OnceStateRun)
369 {
370 return 0;
371 }
372 // If this either hasn't run or is running, hit the slow path.
373 return run_once_slow_path(state, callback);
374}
375
376__END_DECLS
int __cheri_libcall semaphore_get(TimeoutArgument timeout, struct CountingSemaphoreState *semaphore)
Semaphore get operation, decrements the semaphore count.
static void flaglock_lock(struct FlagLockState *lock)
Convenience wrapper to acquire a lock with an unlimited timeout.
Definition locks.h:174
int __cheri_libcall recursivemutex_unlock(struct RecursiveMutexState *mutex)
Unlock a recursive mutex.
int __cheriot_libcall condition_variable_notify(struct ConditionVariableState *conditionVariable, uint32_t waiters)
Signal the condition variable and wake up to the number of waiters specified by the second argument.
static uint16_t flaglock_priority_inheriting_get_owner_thread_id(struct FlagLockState *lock)
Return the thread ID of the owner of the lock.
Definition locks.h:230
static int run_once(struct OnceState *state, void(*callback)())
Definition locks.h:355
void __cheri_libcall flaglock_unlock(struct FlagLockState *lock)
Unlock a flag lock.
static void flaglock_priority_inheriting_lock(struct FlagLockState *lock)
Convenience wrapper to acquire a lock with an unlimited timeout.
Definition locks.h:185
void __cheri_libcall flaglock_upgrade_for_destruction(struct FlagLockState *lock)
Set a flag lock in destruction mode.
void __cheri_libcall ticketlock_unlock(struct TicketLockState *lock)
Release a ticket lock.
int __cheriot_libcall barrier_timed_wait(TimeoutArgument timeout, struct BarrierState *barrier)
Wait on a barrier.
static int barrier_wait(struct BarrierState *barrier)
Helper that calls barrier_timed_wait with an unlimited timeout.
Definition locks.h:305
int __cheri_libcall flaglock_trylock(TimeoutArgument timeout, struct FlagLockState *lock)
Try to lock a flag lock.
int __cheri_libcall flaglock_priority_inheriting_trylock(TimeoutArgument timeout, struct FlagLockState *lock)
Try to lock a flag lock.
int __cheri_libcall recursivemutex_trylock(TimeoutArgument timeout, struct RecursiveMutexState *lock)
Try to acquire a recursive mutex.
OnceStateMachineStates
State-machine states for run-once functions.
Definition locks.h:108
@ OnceStateRun
The function has run to completion.
Definition locks.h:120
@ OnceStateNotRun
The function has not run yet.
Definition locks.h:112
@ OnceStateRunning
The function has started running but has not yet finished.
Definition locks.h:116
int __cheriot_libcall condition_variable_wait(TimeoutArgument t, struct ConditionVariableState *conditionVariable, void *mutex, int(*mutexLock)(TimeoutArgument, void *), int(*mutexUnlock)(void *))
Wait on a condition variable.
int __cheri_libcall semaphore_put(struct CountingSemaphoreState *semaphore)
Semaphore put operation.
void __cheri_libcall ticketlock_lock(struct TicketLockState *lock)
Acquire a ticket lock.
This file implements the C11 and C++23 C atomics interfaces.
State for a barrier.
Definition locks.h:85
_Atomic(uint32_t) remaining
The number of threads that have to rendezvous with this barrier to allow waiters to wake.
Condition variable state.
Definition locks.h:97
_Atomic(uint32_t) sequenceCounter
Sequence counter used to wait and notify waiters.
State for a counting semaphore.
Definition locks.h:64
_Atomic(uint32_t) count
The current counter value.
uint32_t maxCount
The maximum value for the counter.
Definition locks.h:73
State for a flag lock.
Definition locks.h:18
_Atomic(uint32_t) lockWord
The lock word.
Synchronisation state for run-once functions.
Definition locks.h:131
State for a recursive mutex.
Definition locks.h:48
uint16_t depth
The count of the times the lock has been acquired by the same thread.
Definition locks.h:57
struct FlagLockState lock
The underlying lock.
Definition locks.h:52
Ticket locks use two monotonic counters to store the lock state.
Definition locks.h:32
_Atomic(uint32_t) current
The value for the current ticket holder.
_Atomic(uint32_t) next
The value for the next ticket to be granted.
Structure representing a timeout.
Definition timeout.h:47
Functions and types used for thread management.
This file contains the types used for timeouts across scheduler APIs.
union __TimeoutArgument TimeoutArgument
A union to hold timeouts that are passed down to end up in the scheduler.
static const Ticks UnlimitedTimeout
Value indicating an unbounded timeout for use with Timeout.
Definition timeout.h:22