CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
Loading...
Searching...
No Matches
thread.h
Go to the documentation of this file.
1// Copyright Microsoft and CHERIoT Contributors.
2// SPDX-License-Identifier: MIT
3
4/** \file
5 * \brief Functions and types used for thread management.
6 */
7
8#pragma once
9
10#include <cdefs.h>
11#include <compartment.h>
12#include <riscvreg.h>
13#include <stddef.h>
14#include <stdint.h>
15#include <tick_macros.h>
16#include <timeout.h>
17
18__BEGIN_DECLS
19/// the absolute system tick value since boot, 64-bit, assuming never overflows
20typedef struct
21{
22 /// low 32 bits of the system tick
23 uint32_t lo;
24 /// hi 32 bits
25 uint32_t hi;
27[[cheriot::interrupt_state(disabled)]] SystickReturn
28 __cheri_compartment("scheduler") thread_systemtick_get(void);
29
30enum ThreadSleepFlags : uint32_t
31{
32 /**
33 * Sleep for up to the specified timeout, but wake early if there are no
34 * other runnable threads. This allows a high-priority thread to yield for
35 * a fixed number of ticks for lower-priority threads to run, but does not
36 * prevent it from resuming early.
37 */
39};
40
41/**
42 * Sleep for at most the specified timeout (see `timeout.h`).
43 *
44 * The thread becomes runnable once the timeout has expired but a
45 * higher-priority thread may prevent it from actually being scheduled. The
46 * return value is a saturating count of the number of ticks that have elapsed.
47 *
48 * A call of `thread_sleep` with a timeout of zero is equivalent to `yield`,
49 * but reports the time spent sleeping. This requires a cross-compartment call
50 * and return in addition to the overheads of `yield` and so `yield` should be
51 * preferred in contexts where the elapsed time is not required.
52 *
53 * The `flags` parameter is a bitwise OR of `ThreadSleepFlags`.
54 *
55 * A sleeping thread may be woken early if no other threads are runnable or
56 * have earlier timeouts. The thread with the earliest timeout will be woken
57 * first. This can cause a yielding thread to sleep when no other thread is
58 * runnable, but avoids a potential problem where a high-priority thread yields
59 * to allow a low-priority thread to make progress, but then the low-priority
60 * thread does a short sleep. In this case, the desired behaviour is not to
61 * wake the high-priority thread early, but to allow the low-priority thread to
62 * run for the full duration of the high-priority thread's yield.
63 *
64 * If you are using `thread_sleep` to elapse real time, pass
65 * `ThreadSleepNoEarlyWake` as the flags argument to prevent early wakeups.
66 */
67[[cheriot::interrupt_state(disabled)]] int __cheri_compartment("scheduler")
68 thread_sleep(TimeoutArgument timeout, uint32_t flags __if_cxx(= 0));
69
70/**
71 * Return the thread ID of the current running thread.
72 * This is mostly useful where one compartment can run under different threads
73 * and it matters which thread entered this compartment.
74 *
75 * User threads (that is, those defined in the xmake firmware configuration)
76 * are 1-indexed, with 0 indicating primordial idle and scheduling contexts.
77 * User code never runs in these contexts and so anything using this result to
78 * index into a per-thread array may wish to subtract one and avoid allocating
79 * an array element for the idle thread.
80 *
81 * This is implemented in the switcher.
82 */
83uint16_t __cheri_libcall thread_id_get(/*Pre-C23 compatibility*/ __if_c(void));
84
85/**
86 * Returns the number of cycles accounted to the idle thread.
87 *
88 * This API is available only if the scheduler is built with accounting support
89 * enabled.
90 */
91__cheri_compartment("scheduler") uint64_t thread_elapsed_cycles_idle(void);
92
93/**
94 * Returns the number of cycles accounted to the current thread.
95 *
96 * This API is available only if the scheduler is built with accounting
97 * support enabled.
98 */
99__cheri_compartment("scheduler") uint64_t thread_elapsed_cycles_current(void);
100
101/**
102 * Returns the number of user threads (that is, those defined in the xmake
103 * firmware configuration), including threads that have exited.
104 *
105 * This API never fails, but if the trusted stack is exhausted and it cannot
106 * be called then it will return -1. Callers that have not probed the trusted
107 * stack should check for this value.
108 *
109 * The result of this is safe to cache because it will never change over time.
110 */
111__cheri_compartment("scheduler") uint16_t thread_count();
112
113/**
114 * Wait for the specified number of microseconds. This is a busy-wait loop,
115 * not a yield. If the thread is preempted then the wait will be longer than
116 * requested.
117 *
118 * Returns the number of microseconds that the thread actually waited, with an
119 * error margin of the number of instructions used to compute the wait time and
120 * execute the function epilogue.
121 */
122static inline uint64_t thread_microsecond_spin(uint32_t microseconds)
123{
124#ifdef SIMULATION
125 // In simulation builds, pretend that the right amount of time has elapsed.
126 return microseconds;
127#else
128 static const uint32_t CyclesPerMicrosecond = CPU_TIMER_HZ / 1'000'000;
129 __if_cxx(
130 static_assert(CyclesPerMicrosecond > 0, "CPU_TIMER_HZ is too low"););
131 uint64_t start = rdcycle64();
132 // Convert the microseconds to a number of cycles. This does the multiply
133 // first so that we don't end up with zero as a result of the division.
134 uint32_t cycles = microseconds * CyclesPerMicrosecond;
135 uint64_t end = start + cycles;
136 uint64_t current;
137 do
138 {
139 current = rdcycle64();
140 } while (current < end);
141 return (current - start) * CyclesPerMicrosecond;
142#endif
143}
144
145/**
146 * Wait for the specified number of milliseconds. This will yield for periods
147 * that are longer than a scheduler tick and then spin for the remainder of the
148 * time.
149 *
150 * Returns the number of milliseconds that the thread actually waited.
151 */
152static inline uint64_t thread_millisecond_wait(uint32_t milliseconds)
153{
154#ifdef SIMULATION
155 // In simulation builds, just yield once but don't bother trying to do
156 // anything sensible with time. Ignore failures of attempts to sleep.
157 Timeout t = {0, 1};
158 (void)thread_sleep(&t, 0);
159 return milliseconds;
160#else
161 static const uint32_t CyclesPerMillisecond = CPU_TIMER_HZ / 1'000;
162 static const uint32_t CyclesPerTick = CPU_TIMER_HZ / TICK_RATE_HZ;
163 __if_cxx(
164 static_assert(CyclesPerMillisecond > 0, "CPU_TIMER_HZ is too low"););
165 uint32_t cycles = milliseconds * CyclesPerMillisecond;
166 uint64_t start = rdcycle64();
167 uint64_t end = start + cycles;
168 uint64_t current = start;
169 while ((end > current) && (end - current > MS_PER_TICK))
170 {
171 Timeout t = {0, ((uint32_t)(end - current)) / CyclesPerTick};
173 current = rdcycle64();
174 }
175 // Spin for the remaining time.
176 while (current < end)
177 {
178 current = rdcycle64();
179 }
180 current = rdcycle64();
181 return (current - start) / CyclesPerMillisecond;
182#endif
183}
184
185/**
186 * Compute a pointer to a Compartment-Invocation-Local Storage slot.
187 *
188 * By convention, this area holds two pointers, with the 0th reserved for the
189 * unwind.h machinery. Most users of this function should thus use `index` 1.
190 *
191 * See sdk/core/switcher/misc-assembly.h `STACK_ENTRY_RESERVED_SPACE` and its
192 * usage in the switcher and the loader. Also see sdk/include/unwind-assembly.h
193 * `INVOCATION_LOCAL_UNWIND_LIST_OFFSET` and its uses.
194 */
195static inline void **invocation_state_slot(size_t index __if_cxx(= 1))
196{
197 void *csp = __builtin_cheri_stack_get();
198 ptraddr_t top = __builtin_cheri_top_get(csp);
199 return (void **)__builtin_cheri_bounds_set_exact(
200 __builtin_cheri_address_set(csp, top - (index + 1) * sizeof(void *)),
201 sizeof(void *));
202}
203
204__END_DECLS
205
206#ifdef __cplusplus
207
208/**
209 * Return a typed reference to a Compartment-Invocation-Local Storage slot.
210 *
211 * By convention, this area holds two pointers, with the 0th reserved for the
212 * unwind.h machinery. Most users of this function should thus use the default
213 * `Index` of 1.
214 */
215template<typename T, size_t Index = 1>
216__always_inline T *&invocation_state()
217{
218 static_assert((Index == 0) || (Index == 1), "Bad invocation state slot");
219 return *reinterpret_cast<T **>(invocation_state_slot(Index));
220}
221
222#endif
the absolute system tick value since boot, 64-bit, assuming never overflows
Definition thread.h:21
uint32_t hi
hi 32 bits
Definition thread.h:25
uint32_t lo
low 32 bits of the system tick
Definition thread.h:23
Structure representing a timeout.
Definition timeout.h:47
uint64_t thread_elapsed_cycles_current(void)
Returns the number of cycles accounted to the current thread.
static void ** invocation_state_slot(size_t index)
Compute a pointer to a Compartment-Invocation-Local Storage slot.
Definition thread.h:195
static uint64_t thread_microsecond_spin(uint32_t microseconds)
Wait for the specified number of microseconds.
Definition thread.h:122
int thread_sleep(TimeoutArgument timeout, uint32_t flags)
Sleep for at most the specified timeout (see timeout.h).
uint64_t thread_elapsed_cycles_idle(void)
Returns the number of cycles accounted to the idle thread.
ThreadSleepFlags
Definition thread.h:31
@ ThreadSleepNoEarlyWake
Sleep for up to the specified timeout, but wake early if there are no other runnable threads.
Definition thread.h:38
uint16_t thread_count()
Returns the number of user threads (that is, those defined in the xmake firmware configuration),...
uint16_t __cheri_libcall thread_id_get(__if_c(void))
Return the thread ID of the current running thread.
static uint64_t thread_millisecond_wait(uint32_t milliseconds)
Wait for the specified number of milliseconds.
Definition thread.h:152
T *& invocation_state()
Return a typed reference to a Compartment-Invocation-Local Storage slot.
Definition thread.h:216
This file contains the types used for timeouts across scheduler APIs.