blob: 2f156ce876ae0f0250385e577916d00b66528694 [file] [log] [blame]
// Copyright Microsoft and CHERIoT Contributors.
// SPDX-License-Identifier: MIT
#pragma once
#include <cdefs.h>
#include <compartment.h>
#include <riscvreg.h>
#include <stddef.h>
#include <stdint.h>
#include <tick_macros.h>
#include <timeout.h>
__BEGIN_DECLS
/// the absolute system tick value since boot, 64-bit, assuming never overflows
typedef struct
{
/// low 32 bits of the system tick
uint32_t lo;
/// hi 32 bits
uint32_t hi;
} SystickReturn;
[[cheri::interrupt_state(disabled)]] SystickReturn __cheri_compartment("sched")
thread_systemtick_get(void);
enum ThreadSleepFlags : uint32_t
{
/**
* Sleep for up to the specified timeout, but wake early if there are no
* other runnable threads. This allows a high-priority thread to yield for
* a fixed number of ticks for lower-priority threads to run, but does not
* prevent it from resuming early.
*/
ThreadSleepNoEarlyWake = 1 << 0,
};
/**
* Sleep for at most the specified timeout (see `timeout.h`).
*
* The thread becomes runnable once the timeout has expired but a
* higher-priority thread may prevent it from actually being scheduled. The
* return value is a saturating count of the number of ticks that have elapsed.
*
* A call of `thread_sleep` is with a timeout of zero is equivalent to `yield`,
* but reports the time spent sleeping. This requires a cross-domain call and
* return in addition to the overheads of `yield` and so `yield` should be
* preferred in contexts where the elapsed time is not required.
*
* The `flags` parameter is a bitwise OR of `ThreadSleepFlags`.
*
* A sleeping thread may be woken early if no other threads are runnable or
* have earlier timeouts. The thread with the earliest timeout will be woken
* first. This can cause a yielding thread to sleep when no other thread is
* runnable, but avoids a potential problem where a high-priority thread yields
* to allow a low-priority thread to make progress, but then the low-priority
* thread does a short sleep. In this case, the desired behaviour is not to
* wake the high-priority thread early, but to allow the low-priority thread to
* run for the full duration of the high-priority thread's yield.
*
* If you are using `thread_sleep` to elapse real time, pass
* `ThreadSleepNoEarlyWake` as the flags argument to prevent early wakeups.
*/
[[cheri::interrupt_state(disabled)]] int __cheri_compartment("sched")
thread_sleep(struct Timeout *timeout, uint32_t flags __if_cxx(= 0));
/**
* Return the thread ID of the current running thread.
* This is mostly useful where one compartment can run under different threads
* and it matters which thread entered this compartment.
*
* This is implemented in the switcher.
*/
uint16_t __cheri_libcall thread_id_get(void);
/**
* Returns the number of cycles accounted to the idle thread.
*
* This API is available only if the scheduler is built with accounting support
* enabled.
*/
__cheri_compartment("sched") uint64_t thread_elapsed_cycles_idle(void);
/**
* Returns the number of cycles accounted to the current thread.
*
* This API is available only if the scheduler is built with accounting
* support enabled.
*/
__cheri_compartment("sched") uint64_t thread_elapsed_cycles_current(void);
/**
* Returns the number of threads, including threads that have exited.
*
* This API never fails, but if the trusted stack is exhausted and it cannot
* be called then it will return -1. Callers that have not probed the trusted
* stack should check for this value.
*
* The result of this is safe to cache: it will never change over time.
*/
__cheri_compartment("sched") uint16_t thread_count();
/**
* Wait for the specified number of microseconds. This is a busy-wait loop,
* not a yield. If the thread is preempted then the wait will be longer than
* requested.
*
* Returns the number of microseconds that the thread actually waited, with an
* error margin of the number of instructions used to compute the wait time and
* execute the function epilogue.
*/
static inline uint64_t thread_microsecond_spin(uint32_t microseconds)
{
#ifdef SIMULATION
// In simulation builds, pretend that the right amount of time has elapsed.
return microseconds;
#else
static const uint32_t CyclesPerMicrosecond = CPU_TIMER_HZ / 1'000'000;
__if_cxx(
static_assert(CyclesPerMicrosecond > 0, "CPU_TIMER_HZ is too low"););
uint64_t start = rdcycle64();
// Convert the microseconds to a number of cycles. This does the multiply
// first so that we don't end up with zero as a result of the division.
uint32_t cycles = microseconds * CyclesPerMicrosecond;
uint64_t end = start + cycles;
uint64_t current;
do
{
current = rdcycle64();
} while (current < end);
return (current - start) * CyclesPerMicrosecond;
#endif
}
/**
* Wait for the specified number of milliseconds. This will yield for periods
* that are longer than a scheduler tick and then spin for the remainder of the
* time.
*
* Returns the number of milliseconds that the thread actually waited.
*/
static inline uint64_t thread_millisecond_wait(uint32_t milliseconds)
{
#ifdef SIMULATION
// In simulation builds, just yield once but don't bother trying to do
// anything sensible with time.
Timeout t = {0, 1};
thread_sleep(&t, 0);
return milliseconds;
#else
static const uint32_t CyclesPerMillisecond = CPU_TIMER_HZ / 1'000;
static const uint32_t CyclesPerTick = CPU_TIMER_HZ / TICK_RATE_HZ;
__if_cxx(
static_assert(CyclesPerMillisecond > 0, "CPU_TIMER_HZ is too low"););
uint32_t cycles = milliseconds * CyclesPerMillisecond;
uint64_t start = rdcycle64();
uint64_t end = start + cycles;
uint64_t current = start;
while ((end > current) && (end - current > MS_PER_TICK))
{
Timeout t = {0, ((uint32_t)(end - current)) / CyclesPerTick};
thread_sleep(&t, ThreadSleepNoEarlyWake);
current = rdcycle64();
}
// Spin for the remaining time.
while (current < end)
{
current = rdcycle64();
}
current = rdcycle64();
return (current - start) / CyclesPerMillisecond;
#endif
}
__END_DECLS