blob: 45355b625cc8c7500d607f6b4155477c07b4861b [file] [log] [blame]
// Copyright Microsoft and CHERIoT Contributors.
// SPDX-License-Identifier: MIT
#pragma once
#include "plic.h"
#include "thread.h"
#include <platform-timer.hh>
#include <stdint.h>
#include <tick_macros.h>
#include <utils.hh>
namespace
{
/**
* Concept for the interface to setting the system timer.
*/
template<typename T>
concept IsTimer = requires(uint32_t cycles)
{
{T::init()};
{T::setnext(cycles)};
};
static_assert(
IsTimer<TimerCore>,
"Platform's timer implementation does not meet the required interface");
/**
* Timer interface. Provides generic timer functionality to the scheduler,
* wrapping the platform's timer device.
*/
class Timer final : private TimerCore
{
inline static uint64_t lastTickTime = 0;
inline static uint64_t zeroTickTime = 0;
inline static uint32_t accumulatedTickError = 0;
public:
/**
* Perform any setup necessary for the timer device.
*/
static void interrupt_setup()
{
static_assert(TIMERCYCLES_PER_TICK <= UINT32_MAX,
"Cycles per tick can't be represented in 32 bits. "
"Double check your platform config");
init();
zeroTickTime = time();
}
/**
* Expose the timer device's method for returning the current time.
*/
using TimerCore::time;
/**
* Update the timer to fire the next timeout for the thread at the
* front of the queue, or disable the timer if there are no threads
* blocked with a timeout and no threads with the same priority.
*
* The scheduler is a simple RTOS scheduler that does not allow any
* thread to run if a higher-priority thread is runnable. This means
* that we need a timer interrupt in one of two situations:
*
* - We have a thread of the same priority as the current thread and
* we are going to round-robin schedule it.
* - We have a thread of a higher priority than the current thread
* that is currently sleeping on a timeout and need it to preempt the
* current thread when its timeout expires.
*
* We currently over approximate the second condition by making the
* timer fire independent of the priority. If this is every changed,
* some care must be taken to ensure that dynamic priority propagation
* via priority-inheriting futexes behaves correctly.
*
* This should be called after scheduling has changed the list of
* waiting threads.
*/
static void update()
{
auto *thread = Thread::current_get();
bool waitingListIsEmpty = ((Thread::waitingList == nullptr) ||
(Thread::waitingList->expiryTime == -1));
bool threadHasNoPeers =
(thread == nullptr) || (!thread->has_priority_peers());
if (waitingListIsEmpty && threadHasNoPeers)
{
clear();
}
else
{
static constexpr uint64_t DistantFuture =
std::numeric_limits<uint64_t>::max();
uint64_t nextTick = threadHasNoPeers
? DistantFuture
: time() + TIMERCYCLES_PER_TICK;
uint64_t nextTimer = waitingListIsEmpty
? DistantFuture
: Thread::waitingList->expiryTime;
setnext(std::min(nextTick, nextTimer));
}
}
/**
* Wake any threads that were sleeping until a timeout before the
* current time. This also wakes yielded threads if there are no
* runnable threads.
*
* This should be called when a timer interrupt fires.
*/
static void expiretimers()
{
uint64_t now = time();
Thread::ticksSinceBoot =
(now - zeroTickTime) / TIMERCYCLES_PER_TICK;
if (Thread::waitingList == nullptr)
{
return;
}
for (Thread *iter = Thread::waitingList;;)
{
if (iter->expiryTime <= now)
{
Thread *iterNext = iter->timerNext;
iter->ready(Thread::WakeReason::Timer);
iter = iterNext;
if (Thread::waitingList == nullptr ||
iter == Thread::waitingList)
{
break;
}
}
else
{
break;
}
}
// If there are not runnable threads, try to wake a yielded thread
if (!Thread::any_ready())
{
// Look at the first thread. If it is not yielding, there may
// be another thread behind it that is, but that's fine. We
// don't want to encounter situations where (with a
// high-priority A and a low-priority B):
//
// 1. A yields for 5 ticks.
// 2. B starts and does a blocking operation (e.g. try_lock)
// with a 1-tick timeout.
// 3. A wakes up and prevents B from running even though we're
// still in its 5-tick yield period.
if (Thread *head = Thread::waitingList)
{
if (head->is_yielding())
{
Debug::log("Woke thread {} {} cycles early",
head->id_get(),
int64_t(head->expiryTime) - now);
head->ready(Thread::WakeReason::Timer);
}
}
}
}
};
uint64_t expiry_time_for_timeout(uint32_t timeout)
{
if (timeout == -1)
{
return -1;
}
return Timer::time() + (timeout * TIMERCYCLES_PER_TICK);
}
} // namespace