blob: e0cc5e4d8a26975548be2dc8ba3a6d61e63c8853 [file] [log] [blame]
// Copyright Microsoft and CHERIoT Contributors.
// SPDX-License-Identifier: MIT
#pragma once
#include "common.h"
#include <cdefs.h>
#include <priv/riscv.h>
#include <strings.h>
#include <utils.hh>
namespace
{
/// The total number of thread priorities.
constexpr uint16_t ThreadPrioNum = 32U;
// Forward declaration of MultiWaiter so that we can use a pointer to it in
// thread structures.
class MultiWaiterInternal;
uint64_t expiry_time_for_timeout(uint32_t timeout);
template<size_t NPrios>
class ThreadImpl final : private utils::NoCopyNoMove
{
/**
* The number of threads that are currently running. This is
* decremented when a thread exits.
*
* This count does not include the idle thread.
*/
inline static uint16_t threadCount = CONFIG_THREADS_NUM;
static_assert(CONFIG_THREADS_NUM <
std::numeric_limits<decltype(threadCount)>::max());
public:
enum class ThreadState : uint8_t
{
Ready,
Suspended,
/// The thread has exited and should never be scheduled.
Exited
};
enum class WakeReason : uint8_t
{
Timer,
Futex,
/**
* Woken because one or more events registered with a multi-waiter
* object has occurred.
*/
MultiWaiter,
/// Woken up because the data structure is gone.
Delete
};
/**
* The number of timer ticks elapsed since boot. We use uint64_t and
* assume it never overflows. Note that uint64_t is not atomic on
* 32-bit platforms, so this field is always modified with interrupts
* disabled.
*/
static inline uint64_t ticksSinceBoot;
/// All threads that are suspended must be on this list.
static inline ThreadImpl *waitingList;
/// Returns the current running thread.
static ThreadImpl *current_get()
{
return current;
}
/**
* Run the scheduler to pick the next thread to run. This returns a
* sealed capability to the trusted stack of the new thread, ready to be
* installed.
*/
static TrustedStack *schedule(TrustedStack *tstack)
{
ThreadImpl *th = current;
if (th != nullptr)
{
if (th->state == ThreadState::Ready)
{
priorityList[th->priority] = th->next;
}
th->tStackPtr = tstack;
}
else
{
schedTStack = tstack;
}
current = priorityList[highestPriority];
if (current)
{
Debug::Assert(highestPriority == current->priority,
"Thread {} is on the run queue for priority {}, "
"but is currently priority {} (originally {})",
current->threadId,
highestPriority,
current->priority,
current->OriginalPriority);
if (current->priority != current->OriginalPriority)
{
Debug::log(
"Running thread {} with boosted priority ({} from {})",
current->id_get(),
current->priority,
current->OriginalPriority);
}
return current->tStackPtr;
}
return schedTStack;
}
/**
* Returns true if any thread is ready to run.
*/
static bool any_ready()
{
return priorityMap != 0;
}
static uint32_t yield_timed()
{
uint64_t ticksAtStart = ticksSinceBoot;
yield();
uint64_t elapsed = ticksSinceBoot - ticksAtStart;
if (elapsed > std::numeric_limits<uint32_t>::max())
{
Debug::Assert(false, "Thread slept for too long, likely a bug");
return std::numeric_limits<uint32_t>::max();
}
return elapsed;
}
/**
* Yield for up to the specified timeout. May return early if the
* thread is on a sleep queue. The caller should check permissions on
* the timeout parameter before the call, but it will be updated only
* if it is not freed by another thread during this call.
*
* Returns true on timeout, false otherwise.
*/
bool suspend(Timeout *t,
ThreadImpl **newSleepQueue,
bool yieldUnconditionally = false,
bool yieldNotSleep = false)
{
if (t->remaining != 0)
{
suspend(t->remaining, newSleepQueue, yieldNotSleep);
}
if ((t->remaining != 0) || yieldUnconditionally)
{
auto elapsed = yield_timed();
if (CHERI::Capability{t}.is_valid())
{
t->elapse(elapsed);
if (t->remaining > 0)
{
return false;
}
}
}
return true;
}
/**
* The trusted stack for the idle thread. This trusted stack has a
* depth of 0, so we only use it for the register file and no
* compartment calls are allowed for the idle thread.
* This is also a sealed handle from the switcher, which can only be
* used for comparison.
*/
static inline TrustedStack *schedTStack;
ThreadImpl(TrustedStack *tstack, uint16_t threadid, uint16_t priority)
: threadId(threadid),
priority(priority),
OriginalPriority(priority),
expiryTime(-1),
state(ThreadState::Suspended),
isYielding(false),
sleepQueue(nullptr),
tStackPtr(tstack)
{
static_assert(NPrios <
std::numeric_limits<decltype(priority)>::max());
// All threads are created in blocked state.
timer_list_insert(&waitingList);
}
/**
* Ready this thread. Remove self from the timer list, and optionally,
* the list of the resource that this thread was blocked on. If thread
* is readied not by the resource it was blocked on but by a timeout or
* the resource disappearing, do some clean-ups.
* @param the reason why this thread is now able to be scheduled
*/
bool ready(WakeReason reason)
{
int64_t ticksLeft;
bool schedule = false;
// We must be suspended.
Debug::Assert(state == ThreadState::Suspended,
"Waking thread that is in state {}, not suspended",
static_cast<ThreadState>(state));
// First, remove self from the timer waiting list.
timer_list_remove(&waitingList);
if (sleepQueue != nullptr)
{
// We were on a list waiting for some resource. Remove ourselves
// from that list.
list_remove(sleepQueue);
sleepQueue = nullptr;
}
state = ThreadState::Ready;
if (priorityList[priority] == nullptr)
{
// First thread at this priority ready.
priorityMap |= (1U << priority);
if (priority > highestPriority)
{
highestPriority = priority;
schedule = true;
}
}
// If this is the same priority as the current thread, we may need
// to update the timer.
if (priority >= highestPriority)
{
schedule = true;
}
if (reason == WakeReason::Timer || reason == WakeReason::Delete)
{
multiWaiter = nullptr;
}
list_insert(&priorityList[priority]);
isYielding = false;
return schedule;
}
/**
* Mutation-safe walker for a thread list. The first argument is the
* list to walk, the second is the visitor that is invoked with each
* thread. An optional third argument can be used to exit the loop
* early. If this returns `true` then iteration will end before
* reaching the end of the loop.
*
* It is safe to call `ready` on threads visited by this function.
*/
template<typename Visitor,
typename TerminateCondition = decltype([]() { return false; })>
__always_inline void static walk_thread_list(
ThreadImpl *&list,
Visitor &&visitor,
TerminateCondition &&terminateCondition = TerminateCondition{})
{
for (auto *thread = list;
(thread != nullptr) && !terminateCondition();)
{
// Get the next pointer *before* any operation that might
// remove this thread from the list.
auto *next = thread->next;
// The thread queues are actually circularly linked lists to
// avoid a head and tail pointer for each queue, so we spot the
// last thread if it's the end.
next = (list == next) ? nullptr : next;
visitor(thread);
thread = next;
}
}
/**
* Suspend this thread. Take it off the ready list. If it is suspended
* waiting on a resource, add it to the list of that resource. No
* matter what, it has to be added to the timer list.
*/
void suspend(uint32_t waitTicks,
ThreadImpl **newSleepQueue,
bool yieldNotSleep = false)
{
isYielding = yieldNotSleep;
Debug::Assert(state == ThreadState::Ready,
"Suspending thread that is in state {}, not ready",
static_cast<ThreadState>(state));
list_remove(&priorityList[priority]);
state = ThreadState::Suspended;
priority_map_remove();
if (newSleepQueue != nullptr)
{
list_insert(newSleepQueue);
sleepQueue = newSleepQueue;
}
expiryTime = expiry_time_for_timeout(waitTicks);
timer_list_insert(&waitingList);
}
/**
* Boost the thread's thread to `newPriority` if that is larger than
* the original priority or reset to the original priority if not.
*/
void priority_boost(uint8_t newPriority)
{
newPriority = std::max(newPriority, OriginalPriority);
if (newPriority == priority)
{
return;
}
// If this thread is currently runnable, move it to the right run
// queue.
if (state == ThreadState::Ready)
{
list_remove(&priorityList[priority]);
list_insert(&priorityList[newPriority]);
priorityMap |= 1U << newPriority;
priority_map_remove();
}
// Sleep queues are sorted by priority. If we're on a sleep queue,
// remove ourself and add ourself back in the right place.
if (sleepQueue != nullptr)
{
list_remove(sleepQueue);
}
priority = newPriority;
if (sleepQueue != nullptr)
{
list_insert(sleepQueue);
}
}
/**
* Returns true if this thread is running with the highest priority of
* any runnable threads.
*/
bool is_highest_priority()
{
Debug::Assert(priority <= highestPriority,
"Priority ({}) should not be higher than the highest "
"priority ({})!",
priority,
highestPriority);
return priority == highestPriority;
}
/**
* Cause the current thread to exit. It will be removed from the
* scheduling queue. The caller is responsible for invoking the
* scheduler to run another thread.
*
* Returns true if this was the last thread, false otherwise.
*/
static bool exit()
{
Debug::log("Thread exited, {} threads remaining", threadCount - 1);
current->list_remove(&priorityList[current->priority]);
current->state = ThreadState::Exited;
return (--threadCount) == 0;
}
/**
* Insert self into a list of threads. headPtr can be nullptr if we are
* the first one on this list. The list is sorted by priority. Higher
* priority is at head, lower at tail.
*/
void list_insert(ThreadImpl **headPtr)
{
ThreadImpl *head = *headPtr;
if (head == nullptr)
{
next = prev = *headPtr = this;
}
else
{
ThreadImpl *iter = head->prev;
ThreadImpl *iterNext;
// Go back from tail, and stop at the first Thread whose
// Priority >= ours.
while (iter->priority < priority)
{
iter = iter->prev;
if (iter == head->prev)
{
break;
}
}
iterNext = iter->next;
iter->next = iterNext->prev = this;
next = iterNext;
prev = iter;
if (priority > head->priority)
{
*headPtr = this;
}
}
}
/// Same as list_insert(), but sorted on ExpiryTime.
void timer_list_insert(ThreadImpl **headPtr)
{
ThreadImpl *head = *headPtr;
Debug::Assert(state == ThreadState::Suspended,
"Inserting thread into timer list that is in state "
"{}, not suspended",
static_cast<ThreadState>(state));
if (head == nullptr)
{
timerNext = timerPrev = *headPtr = this;
}
else
{
ThreadImpl *iter = head->timerPrev;
ThreadImpl *iterNext;
// Go back from tail, and stop at the first Thread whose expiry
// <= ours.
while (iter->expiryTime > expiryTime)
{
iter = iter->timerPrev;
if (iter == head->timerPrev)
{
break;
}
}
iterNext = iter->timerNext;
iter->timerNext = iterNext->timerPrev = this;
timerNext = iterNext;
timerPrev = iter;
if (expiryTime < head->expiryTime)
{
*headPtr = this;
}
}
}
/// Remove self from the list headPtr points to.
void list_remove(ThreadImpl **headPtr)
{
ThreadImpl *head = *headPtr;
if (next == this)
{
// We are the only one left on this list, clear the head.
Debug::Assert(prev == this,
"Invalid loop in thread list. Next pointer "
"points to this ({}) but previous pointer is {}",
this,
prev);
*headPtr = nullptr;
}
else
{
ThreadImpl *prevThread = prev;
ThreadImpl *nextThread = next;
prevThread->next = nextThread;
nextThread->prev = prevThread;
if (head == this)
{
*headPtr = next;
}
}
next = prev = nullptr;
}
/// Same as list_remove(), but for the timer list.
void timer_list_remove(ThreadImpl **headPtr)
{
ThreadImpl *head = *headPtr;
if (timerNext == this)
{
// We are the only one left on this list, clear the head.
Debug::Assert(timerPrev == this,
"Invalid loop in thread list. Next pointer "
"points to this ({}) but previous pointer is {}",
this,
timerPrev);
*headPtr = nullptr;
}
else
{
ThreadImpl *prevThread = timerPrev;
ThreadImpl *nextThread = timerNext;
prevThread->timerNext = nextThread;
nextThread->timerPrev = prevThread;
if (head == this)
{
*headPtr = timerNext;
}
}
timerNext = timerPrev = nullptr;
}
uint16_t id_get()
{
return threadId;
}
uint8_t priority_get()
{
return priority;
}
bool is_ready()
{
return state == ThreadState::Ready;
}
bool is_yielding()
{
return isYielding;
}
/**
* Returns true if there are other runnable threads with the same
* priority as this thread.
*/
bool has_priority_peers()
{
Debug::Assert(state == ThreadState::Ready,
"Checking for peers on thread that is in state {}, "
"not ready",
static_cast<ThreadState>(state));
return next != this;
}
~ThreadImpl()
{
// We have static definition of threads. We only create threads in
// the boot loader and never destroy threads.
panic();
}
/// Linked list fields. They are nullptr, unless blocked on a resource,
/// which then get linked to the list of that resource.
///@{
ThreadImpl *prev;
ThreadImpl *next;
///@}
/// Linked list fields to the global timer list, when this thread is
/// blocked with a timeout.
///@{
ThreadImpl *timerPrev;
ThreadImpl *timerNext;
///@}
/// Pointer to the list of the resource this thread is blocked on.
ThreadImpl **sleepQueue;
/// If suspended, when will this thread expire. The maximum value is
/// special-cased to mean blocked indefinitely.
uint64_t expiryTime;
/// The number of cycles that this thread has been scheduled for.
uint64_t cycles;
/// The number of cycles accounted to the idle thread.
static inline uint64_t idleThreadCycles;
union
{
/// State associated with waiting on a futex.
struct
{
/**
* If this thread is blocked on a futex, this holds the address
* of the futex. This is set to 0 if woken via timeout.
*/
ptraddr_t futexWaitAddress;
/**
* The thread that we're priority boosting. This is ignored if
* `futexPriorityInheriting` is false.
*/
uint16_t futexPriorityBoostedThread : 16;
/**
* If this thread is waiting on a futex, should it be priority
* boosting the current holder of the futex?
*/
bool futexPriorityInheriting : 1;
};
/**
* If this thread is blocked on a multiwaiter, this holds the
* address of the multiwaiter object.
*/
MultiWaiterInternal *multiWaiter;
};
TrustedStack *tStackPtr;
private:
/**
* Helper to remove a thread from the priority map and update the
* highest priority, if it was the last runnable thread at that
* priority level.
*/
void priority_map_remove()
{
if (priorityList[priority] == nullptr)
{
// We just removed the last ready thread at this priority.
priorityMap &= ~(1U << priority);
if (priorityMap == 0)
{
highestPriority = 0;
}
else
{
// clz is only for 32-bit.
static_assert(NPrios <= 32);
uint_fast16_t topZeroes = clz(priorityMap);
highestPriority = NPrios - 1 - topZeroes;
}
}
}
/// the current runnning thread
static inline ThreadImpl *current;
/// NPrios number of lists, each linking the threads of this priority
static inline ThreadImpl *priorityList[NPrios];
/// A bit field indicating the presence of ready threads. A set bit at
/// bit index N means there's at least one thread ready with priority N.
static inline uint32_t priorityMap;
/// the highest priority of all the current threads that are ready
static inline uint16_t highestPriority;
uint16_t threadId;
/**
* The current priority level for this thread. This may be influenced
* by priority inheritance.
*/
uint8_t priority;
/// The original priority level for this thread. This never changes.
const uint8_t OriginalPriority;
ThreadState state : 2;
/**
* If the thread is yielding, it may be scheduled before its timeout
* expires, as long as no other threads are runnable or sleeping with
* shorter timeouts.
*/
bool isYielding : 1;
};
using Thread = ThreadImpl<ThreadPrioNum>;
} // namespace