CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
Loading...
Searching...
No Matches
locks.hh
Go to the documentation of this file.
1#pragma once
2
3#include <cdefs.h>
4#include <cheriot-atomic.hh>
5#include <debug.hh>
6#include <errno.h>
7#include <futex.h>
8#include <locks.h>
9#include <thread.h>
10
11/**
12 * \file
13 * \brief C++ wrappers for synchronisation primitives.
14 */
15
16static constexpr bool DebugLocks =
17#ifdef DEBUG_LOCKS
18 DEBUG_LOCKS
19#else
20 false
21#endif
22 ;
23using LockDebug = ConditionalDebug<DebugLocks, "Locking">;
24
25__clang_ignored_warning_push("-Watomic-alignment");
26
27/**
28 * A simple flag log, wrapping an atomic word used with the `futex` calls.
29 * Threads blocked on this will be woken in priority order. If
30 * `IsPriorityInherited` is set, priority is inherited by waiters to avoid
31 * priority inversion issues.
32 *
33 * The lock word that this wraps is directly accessibly by any malicious
34 * compartment that has a reference to this thread. If this is a security
35 * concern then you may have other problems: a malicious compartment with
36 * access to a mutex's interface (irrespective of the underlying
37 * implementation) can cause deadlock by spuriously acquiring a lock or cause
38 * data corruption via races by spuriously releasing it. Anything that
39 * requires mutual exclusion in the presence of mutual distrust should
40 * consider an using a lock manager compartment with an API that returns a
41 * single-use capability to unlock on any lock call.
42 *
43 * Flag locks are not recursive; if needed, use a `RecursiveMutex` instead.
44 */
45template<bool IsPriorityInherited>
47{
48 FlagLockState state;
49
50 /**
51 * Attempt to acquire the lock, blocking until a timeout specified by the
52 * `timeout` parameter has expired.
53 *
54 * Returns an errno value.
55 */
56 __always_inline int try_lock_internal(TimeoutArgument timeout)
57 {
58 if constexpr (IsPriorityInherited)
59 {
60 return flaglock_priority_inheriting_trylock(timeout, &state);
61 }
62 else
63 {
64 return flaglock_trylock(timeout, &state);
65 }
66 }
67
68 public:
69 /**
70 * Attempt to acquire the lock, blocking until a timeout specified by the
71 * `timeout` parameter has expired.
72 *
73 * Returns `true` if and only if the lock transitioned from being unheld to
74 * held by the current thread.
75 */
76 __always_inline bool try_lock(TimeoutArgument timeout)
77 {
78 return try_lock_internal(timeout) == 0;
79 }
80
81 /**
82 * Try to acquire the lock, do not block.
83 *
84 * Returns `true` if and only if the lock transitioned from being unheld to
85 * held by the current thread.
86 */
87 __always_inline bool try_lock()
88 {
89 Timeout t{0};
90 return try_lock(&t);
91 }
92
93 /**
94 * Acquire the lock, potentially blocking forever.
95 *
96 * This function returns `void` despite that this is a bad idea.
97 * Because the C++ standard library has `void`-returning `::lock()` methods,
98 * templated callers would not check a return value,
99 * making it unsafe to return in the event of failed lock acquisition.
100 * Failures instead manifest as tripping a `Debug::Invariant`,
101 * causing this function not to return (and yet not block forever).
102 * Thus, it is not safe with locks that may be put into "destrucion mode"
103 * (see `flaglock_upgrade_for_destruction`).
104 * Attempting to recursively `lock` a held priority inheriting flag lock
105 * (acquired by either `lock` or `try_lock`) will trip the same Invariant.
106 * (Recall that flag locks explicitly do not support recursive acquisition;
107 * the failure mode is, however, not one of blocking forever.)
108 * In general, defensive code should prefer `try_lock`.
109 */
110 __always_inline void lock()
111 {
113 auto res = try_lock_internal(&t);
114 LockDebug::Invariant(res == 0, "FlagLock lock() failed: {}", res);
115 }
116
117 /**
118 * Release the lock.
119 *
120 * Note: This does not check that the lock is owned by the calling thread.
121 */
122 __always_inline void unlock()
123 {
124 flaglock_unlock(&state);
125 }
126
127 /**
128 * Set the lock in destruction mode. See the documentation of
129 * `flaglock_upgrade_for_destruction` for more information.
130 */
131 __always_inline void upgrade_for_destruction()
132 {
134 }
135
136 /**
137 * Return the thread ID of the owner of the lock.
138 *
139 * This is only available for priority inherited locks, as this is the
140 * only case where we store the thread ID of the owner. See the
141 * documentation of `flaglock_priority_inheriting_get_owner_thread_id`
142 * for more information.
143 */
144 __always_inline uint16_t get_owner_thread_id()
145 requires(IsPriorityInherited)
146 {
148 }
149};
150
151/**
152 * Priority-inheriting recursive mutex. This can be acquired multiple times
153 * from the same thread.
154 */
156{
157 /// State for the underling recursive mutex.
159
160 public:
161 /**
162 * Attempt to acquire the lock, blocking until a timeout specified by the
163 * `timeout` parameter has expired.
164 */
165 __always_inline bool try_lock(TimeoutArgument timeout)
166 {
167 return recursivemutex_trylock(timeout, &state) == 0;
168 }
169
170 /**
171 * Try to acquire the lock, do not block.
172 */
173 __always_inline bool try_lock()
174 {
175 Timeout t{0};
176 return try_lock(&t);
177 }
178
179 /**
180 * Acquire the lock, potentially blocking forever.
181 */
182 __always_inline void lock()
183 {
185 try_lock(&t);
186 }
187
188 /**
189 * Release the lock.
190 *
191 * Note: This does not check that the lock is owned by the calling thread.
192 */
193 __always_inline void unlock()
194 {
195 recursivemutex_unlock(&state);
196 }
197
198 /**
199 * Set the lock in destruction mode. See the documentation of
200 * `flaglock_upgrade_for_destruction` for more information.
201 */
202 __always_inline void upgrade_for_destruction()
203 {
205 }
206};
207
208/**
209 * A simple ticket lock.
210 *
211 * A ticket lock ensures that threads that arrive are serviced in order,
212 * without regard for priorities. It has no mechanism for tracking tickets
213 * that are discarded and so does not implement a `try_lock` API.
214 */
216{
217 TicketLockState state;
218
219 public:
220 /**
221 * Acquire the lock.
222 */
223 __always_inline void lock()
224 {
225 ticketlock_lock(&state);
226 }
227
228 /**
229 * Release the lock.
230 *
231 * Note: This does not check that the lock is owned by the calling thread.
232 */
233 __always_inline void unlock()
234 {
235 ticketlock_unlock(&state);
236 }
237};
238
239/**
240 * Class that implements the locking concept but does not perform locking.
241 * This is intended to be used with templated data structures that support
242 * locking, for instantiations that do not require locking.
243 */
245{
246 public:
247 /**
248 * Attempt to acquire the lock with a timeout. Always succeeds.
249 */
250 bool try_lock(TimeoutArgument timeout)
251 {
252 return true;
253 }
254
255 /**
256 * Try to acquire the lock, do not block. Always succeeds.
257 */
258 bool try_lock()
259 {
260 return true;
261 }
262
263 /**
264 * Acquire the lock. Always succeeds
265 */
266 void lock() {}
267
268 /**
269 * Release the lock. Does nothing.
270 */
271 void unlock() {}
272};
273
274using FlagLock = FlagLockGeneric<false>;
275using FlagLockPriorityInherited = FlagLockGeneric<true>;
276
277template<typename T>
278concept Lockable = requires(T l) {
279 { l.lock() };
280 { l.unlock() };
281};
282
283template<typename T>
284concept TryLockable = Lockable<T> && requires(T l, TimeoutArgument t) {
285 { l.try_lock(t) } -> std::same_as<bool>;
286};
287
288static_assert(TryLockable<NoLock>);
289static_assert(TryLockable<FlagLock>);
291static_assert(Lockable<TicketLock>);
292
293/**
294 * A simple RAII type that owns a lock.
295 */
296template<typename Lock>
298{
299 /// A reference to the managed lock
300 Lock *wrappedLock;
301
302 /// Flag indicating whether the lock is owned.
303 bool isOwned;
304
305 public:
306 /**
307 * Constructor, acquires the lock with unbounded wait.
308 *
309 * The lock to be wrapped must not be held by the calling thread on entry.
310 * Further, for this constructor to be safe to invoke,
311 * the `Lock` type must have an unfailable `lock` member;
312 * for example, this precludes the use of `FlagLock`-s
313 * that may be placed in "destruction mode".
314 *
315 * In general, prefer the `LockGuard` constructor that takes a `Timeout`.
316 */
317 [[nodiscard]] explicit LockGuard(Lock &lock)
318 : wrappedLock(&lock), isOwned(true)
319 {
320 wrappedLock->lock();
321 }
322
323 /**
324 * Constructor, attempts to acquire the lock with a timeout.
325 *
326 * The lock to be wrapped must not be held by the calling thread on entry.
327 */
328 [[nodiscard]] explicit LockGuard(Lock &lock, TimeoutArgument timeout)
329 requires(TryLockable<Lock>)
330 : wrappedLock(&lock), isOwned(false)
331 {
332 try_lock(timeout);
333 }
334
335 /// Move constructor, transfers ownership of the lock.
336 [[nodiscard]] explicit LockGuard(LockGuard &&guard)
337 : wrappedLock(guard.wrappedLock), isOwned(guard.isOwned)
338 {
339 guard.wrappedLock = nullptr;
340 guard.isOwned = false;
341 }
342
343 /**
344 * Explicitly lock the wrapped lock.
345 *
346 * The wrapped lock must not be held by the calling thread on entry.
347 * For this method to be safe to call,
348 * the `Lock` type must have an unfailable `lock` member;
349 * for example, this precludes the use of `FlagLock`-s
350 * that may be placed in "destruction mode".
351 * In general, prefer `try_lock`.
352 */
353 void lock()
354 {
355 LockDebug::Assert(!isOwned, "Trying to lock an already-locked lock");
356 wrappedLock->lock();
357 isOwned = true;
358 }
359
360 /**
361 * Explicitly lock the wrapped lock. Must be called with the lock locked by
362 * this wrapper.
363 */
364 void unlock()
365 {
366 LockDebug::Assert(isOwned, "Trying to unlock an unlocked lock");
367 wrappedLock->unlock();
368 isOwned = false;
369 }
370
371 /**
372 * Unwrap the lock without unlocking it. This is useful to call before
373 * destroying a lock.
374 */
375 void release()
376 {
377 wrappedLock = nullptr;
378 isOwned = false;
379 }
380
381 /**
382 * If the wrapped lock type supports locking with a timeout,
383 * try to lock it with the specified timeout.
384 *
385 * The wrapped lock must not be held by the calling thread on entry.
386 * Returns true if the lock has been acquired, false otherwise.
387 */
388 bool try_lock(TimeoutArgument timeout)
389 requires(TryLockable<Lock>)
390 {
391 LockDebug::Assert(!isOwned, "Trying to lock an already-locked lock");
392 isOwned = wrappedLock->try_lock(timeout);
393 return isOwned;
394 }
395
396 /// Destructor, releases the lock.
398 {
399 if (isOwned)
400 {
401 wrappedLock->unlock();
402 }
403 }
404
405 /**
406 * Conversion to bool. Returns true if this guard owns the lock, false
407 * otherwise. This allows lock guards to be used with a timeout in
408 * conditional blocks, such as:
409 *
410 * ```
411 * if (LockGuard g{lock, timeout})
412 * {
413 * // Run this code if we acquired the lock, releasing the lock at the
414 * end.
415 * }
416 * else
417 * {
418 * // Run this code if we did not acquire the lock.
419 * }
420 * ```
421 */
422 operator bool()
423 {
424 return isOwned;
425 }
426
427 /**
428 * Drop and reacquire the lock around a yield
429 */
430 int yield(TimeoutArgument t, uint32_t ticks = 1)
431 {
432 unlock();
433
434 Timeout smallSleep{ticks};
435 if (int sleepRes = thread_sleep(&smallSleep); sleepRes < 0)
436 {
437 return sleepRes;
438 }
439
440 t.elapse_from(smallSleep);
441
442 return try_lock(t);
443 }
444};
445
446/**
447 * Condition variable C++ wrapper.
448 */
450{
451 /**
452 * The state for the underlying condition variable.
453 */
455
456 public:
457 /**
458 * Wake one waiter. Propagates errors from `condition_variable_notify`.
459 */
460 int signal()
461 {
462 return condition_variable_notify(&state, 1);
463 }
464
465 /**
466 * Wake all waiters. Propagates errors from `condition_variable_notify`.
467 */
469 {
470 return condition_variable_notify(&state,
471 std::numeric_limits<uint32_t>::max());
472 }
473
474 /**
475 * Atomically release `mutex`, wait until the condition variable is
476 * signalled, and reacquire the mutex. This will return 0 if the sequence
477 * completes correctly, or `-ETIMEDOUT` if waiting or reacquiring the lock
478 * failed due to a timeout.
479 */
480 template<TryLockable Mutex>
481 int wait(TimeoutArgument t, Mutex &mutex)
482 {
483 auto lock = [](TimeoutArgument t, void *m) {
484 return static_cast<Mutex *>(m)->try_lock(t) ? 0 : -ETIMEDOUT;
485 };
486 auto unlock = [](void *m) {
487 static_cast<Mutex *>(m)->unlock();
488 return 0;
489 };
490 return condition_variable_wait(t, &state, &mutex, lock, unlock);
491 }
492
493 /**
494 * Atomically release `mutex`, wait until the condition variable is
495 * signalled, and reacquire the mutex. This variant uses locks with no
496 * timeout and so may block for longer than the specified timeout while
497 * reacquiring the lock. It will not attempt to reacquire the lock if the
498 * timeout happens before the condition variable is signalled, so can still
499 * fail in the same ways as the normal overload.
500 */
501 template<Lockable Mutex>
502 int wait(TimeoutArgument t, Mutex &mutex)
503 requires(!TryLockable<Mutex>)
504 {
505 auto lock = [](TimeoutArgument t, void *m) {
506 static_cast<Mutex *>(m)->lock();
507 return 0;
508 };
509 auto unlock = [](void *m) {
510 static_cast<Mutex *>(m)->unlock();
511 return 0;
512 };
513 return condition_variable_wait(t, &state, &mutex, lock, unlock);
514 }
515};
516
517__clang_ignored_warning_pop();
Condition variable C++ wrapper.
Definition locks.hh:450
int broadcast()
Wake all waiters.
Definition locks.hh:468
int signal()
Wake one waiter.
Definition locks.hh:460
int wait(TimeoutArgument t, Mutex &mutex)
Atomically release mutex, wait until the condition variable is signalled, and reacquire the mutex.
Definition locks.hh:502
int wait(TimeoutArgument t, Mutex &mutex)
Atomically release mutex, wait until the condition variable is signalled, and reacquire the mutex.
Definition locks.hh:481
A simple flag log, wrapping an atomic word used with the futex calls.
Definition locks.hh:47
void upgrade_for_destruction()
Set the lock in destruction mode.
Definition locks.hh:131
void unlock()
Release the lock.
Definition locks.hh:122
void lock()
Acquire the lock, potentially blocking forever.
Definition locks.hh:110
bool try_lock()
Try to acquire the lock, do not block.
Definition locks.hh:87
uint16_t get_owner_thread_id()
Return the thread ID of the owner of the lock.
Definition locks.hh:144
bool try_lock(TimeoutArgument timeout)
Attempt to acquire the lock, blocking until a timeout specified by the timeout parameter has expired.
Definition locks.hh:76
LockGuard(LockGuard &&guard)
Move constructor, transfers ownership of the lock.
Definition locks.hh:336
int yield(TimeoutArgument t, uint32_t ticks=1)
Drop and reacquire the lock around a yield.
Definition locks.hh:430
void release()
Unwrap the lock without unlocking it.
Definition locks.hh:375
~LockGuard()
Destructor, releases the lock.
Definition locks.hh:397
LockGuard(Lock &lock, TimeoutArgument timeout)
Constructor, attempts to acquire the lock with a timeout.
Definition locks.hh:328
void unlock()
Explicitly lock the wrapped lock.
Definition locks.hh:364
LockGuard(Lock &lock)
Constructor, acquires the lock with unbounded wait.
Definition locks.hh:317
void lock()
Explicitly lock the wrapped lock.
Definition locks.hh:353
bool try_lock(TimeoutArgument timeout)
If the wrapped lock type supports locking with a timeout, try to lock it with the specified timeout.
Definition locks.hh:388
Class that implements the locking concept but does not perform locking.
Definition locks.hh:245
void unlock()
Release the lock.
Definition locks.hh:271
bool try_lock(TimeoutArgument timeout)
Attempt to acquire the lock with a timeout.
Definition locks.hh:250
bool try_lock()
Try to acquire the lock, do not block.
Definition locks.hh:258
void lock()
Acquire the lock.
Definition locks.hh:266
Priority-inheriting recursive mutex.
Definition locks.hh:156
void upgrade_for_destruction()
Set the lock in destruction mode.
Definition locks.hh:202
void unlock()
Release the lock.
Definition locks.hh:193
bool try_lock()
Try to acquire the lock, do not block.
Definition locks.hh:173
bool try_lock(TimeoutArgument timeout)
Attempt to acquire the lock, blocking until a timeout specified by the timeout parameter has expired.
Definition locks.hh:165
void lock()
Acquire the lock, potentially blocking forever.
Definition locks.hh:182
A simple ticket lock.
Definition locks.hh:216
void lock()
Acquire the lock.
Definition locks.hh:223
void unlock()
Release the lock.
Definition locks.hh:233
C++ APIs for assertions, invariants, and writing formatted debug messages to a UART.
Futex (compare-and-wait) APIs.
C compatible interface to synchronisation primitives.
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.
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 __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.
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.
void __cheri_libcall ticketlock_lock(struct TicketLockState *lock)
Acquire a ticket lock.
Condition variable state.
Definition locks.h:97
State for a flag lock.
Definition locks.h:18
State for a recursive mutex.
Definition locks.h:48
Ticket locks use two monotonic counters to store the lock state.
Definition locks.h:32
Structure representing a timeout.
Definition timeout.h:47
Functions and types used for thread management.
int thread_sleep(TimeoutArgument timeout, uint32_t flags)
Sleep for at most the specified timeout (see timeout.h).
static const Ticks UnlimitedTimeout
Value indicating an unbounded timeout for use with Timeout.
Definition timeout.h:22