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
349condition_variable_wait(TimeoutArgument t,
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
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.
static int run_once(struct OnceState *state, void(*callback)(__if_c(void)))
Definition locks.h:355
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.
static const Ticks UnlimitedTimeout
Value indicating an unbounded timeout for use with Timeout.
Definition timeout.h:22
union __if_c() __TimeoutArgument
A union to hold timeouts that are passed down to end up in the scheduler.
Definition timeout.h:116