| // 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 |