kernel/platform/
scheduler_timer.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Scheduler Timer for enforcing Process Timeslices
6//!
7//! Interface for use by the Kernel to configure timers which can preempt
8//! userspace processes.
9
10use crate::hil::time::{self, Frequency, Ticks};
11
12use core::num::NonZeroU32;
13
14/// Interface for the system scheduler timer.
15///
16/// A system scheduler timer provides a countdown timer to enforce process
17/// scheduling time quanta. Implementations should have consistent timing while
18/// the CPU is active, but need not operate during sleep. Note, many scheduler
19/// implementations also charge time spent running the kernel on behalf of the
20/// process against the process time quantum.
21///
22/// The primary requirement an implementation of this interface must satisfy is
23/// it must be capable of generating an interrupt when the timer expires. This
24/// interrupt will interrupt the executing process, returning control to the
25/// kernel, and allowing the scheduler to make decisions about what to run next.
26///
27/// On most chips, this interface will be implemented by a core peripheral (e.g.
28/// the ARM core SysTick peripheral). However, some chips lack this optional
29/// peripheral, in which case it might be implemented by another timer or alarm
30/// peripheral, or require virtualization on top of a shared hardware timer.
31///
32/// The `SchedulerTimer` interface is carefully designed to be rather general to
33/// support the various implementations required on different hardware
34/// platforms. The general operation is the kernel will start a timer, which
35/// starts the time quantum assigned to a process. While the process is running,
36/// the kernel will arm the timer, telling the implementation it must ensure
37/// that an interrupt will occur when the time quantum is exhausted. When the
38/// process has stopped running, the kernel will disarm the timer, indicating to
39/// the implementation that an interrupt is no longer required. To check if the
40/// process has exhausted its time quantum the kernel will explicitly ask the
41/// implementation. The kernel itself does not expect to get an interrupt to
42/// handle when the time quantum is exhausted. This is because the time quantum
43/// may end while the kernel itself is running, and the kernel does not need to
44/// effectively preempt itself.
45///
46/// The `arm()` and `disarm()` functions in this interface serve as an optional
47/// optimization opportunity. This pair allows an implementation to only enable
48/// the interrupt when it is strictly necessary, i.e. while the process is
49/// actually executing. However, a correct implementation can have interrupts
50/// enabled anytime the scheduler timer has been started. What the
51/// implementation must ensure is that the interrupt is enabled when `arm()` is
52/// called.
53///
54/// Implementations must take care when using interrupts. Since the
55/// `SchedulerTimer` is used in the core kernel loop and scheduler, top half
56/// interrupt handlers may not have executed before `SchedulerTimer` functions
57/// are called. In particular, implementations on top of virtualized timers may
58/// receive the interrupt fired upcall "late" (i.e. after the kernel calls
59/// `has_expired()`). Implementations should ensure that they can reliably check
60/// for timeslice expirations.
61pub trait SchedulerTimer {
62    /// Start a timer for a process timeslice. The `us` argument is the length
63    /// of the timeslice in microseconds.
64    ///
65    /// This must set a timer for an interval as close as possible to the given
66    /// interval in microseconds. Interrupts do not need to be enabled. However,
67    /// if the implementation cannot separate time keeping from interrupt
68    /// generation, the implementation of `start()` should enable interrupts and
69    /// leave them enabled anytime the timer is active.
70    ///
71    /// Callers can assume at least a 24-bit wide clock. Specific timing is
72    /// dependent on the driving clock. For ARM boards with a dedicated SysTick
73    /// peripheral, increments of 10ms are most accurate thanks to additional
74    /// hardware support for this value. ARM SysTick supports intervals up to
75    /// 400ms.
76    fn start(&self, us: NonZeroU32);
77
78    /// Reset the SchedulerTimer.
79    ///
80    /// This must reset the timer, and can safely disable it and put it in a low
81    /// power state. Calling any function other than `start()` immediately after
82    /// `reset()` is invalid.
83    ///
84    /// Implementations _should_ disable the timer and put it in a lower power
85    /// state. However, not all implementations will be able to guarantee this
86    /// (for example depending on the underlying hardware or if the timer is
87    /// implemented on top of a virtualized timer).
88    fn reset(&self);
89
90    /// Arm the SchedulerTimer timer and ensure an interrupt will be generated.
91    ///
92    /// The timer must already be started by calling `start()`. This function
93    /// guarantees that an interrupt will be generated when the already started
94    /// timer expires. This interrupt will preempt the running userspace
95    /// process.
96    ///
97    /// If the interrupt is already enabled when `arm()` is called, this
98    /// function should be a no-op implementation.
99    fn arm(&self);
100
101    /// Disarm the SchedulerTimer timer indicating an interrupt is no longer
102    /// required.
103    ///
104    /// This does not stop the timer, but indicates to the SchedulerTimer that
105    /// an interrupt is no longer required (i.e. the process is no longer
106    /// executing). By not requiring an interrupt this may allow certain
107    /// implementations to be more efficient by removing the overhead of
108    /// handling the interrupt.
109    ///
110    /// If the implementation cannot disable the interrupt without stopping the
111    /// time keeping mechanism, this function should be a no-op implementation.
112    fn disarm(&self);
113
114    /// Return the number of microseconds remaining in the process's timeslice
115    /// if the timeslice is still active.
116    ///
117    /// If the timeslice is still active, this returns `Some()` with the number
118    /// of microseconds remaining in the timeslice. If the timeslice has
119    /// expired, this returns `None`.
120    ///
121    /// This function may not be called after it has returned `None` (signifying
122    /// the timeslice has expired) for a given timeslice until `start()` is
123    /// called again (to start a new timeslice). If `get_remaining_us()` is
124    /// called again after returning `None` without an intervening call to
125    /// `start()`, the return value is unspecified and implementations may
126    /// return whatever they like.
127    fn get_remaining_us(&self) -> Option<NonZeroU32>;
128}
129
130/// A dummy `SchedulerTimer` implementation in which the timer never expires.
131///
132/// Using this implementation is functional, but will mean the scheduler cannot
133/// interrupt non-yielding processes.
134impl SchedulerTimer for () {
135    fn reset(&self) {}
136
137    fn start(&self, _: NonZeroU32) {}
138
139    fn disarm(&self) {}
140
141    fn arm(&self) {}
142
143    fn get_remaining_us(&self) -> Option<NonZeroU32> {
144        NonZeroU32::new(10000) // choose arbitrary large value
145    }
146}
147
148/// Implementation of SchedulerTimer trait on top of a virtual alarm.
149///
150/// Currently, this implementation depends slightly on the virtual alarm
151/// implementation in capsules -- namely it assumes that get_alarm will still
152/// return the passed value even after the timer is disarmed. Thus this should
153/// only be implemented with a virtual alarm. If a dedicated hardware timer is
154/// available, it is more performant to implement the scheduler timer directly
155/// for that hardware peripheral without the alarm abstraction in between.
156///
157/// This mostly handles conversions from wall time, the required inputs to the
158/// trait, to ticks, which are used to track time for alarms.
159pub struct VirtualSchedulerTimer<A: 'static + time::Alarm<'static>> {
160    alarm: &'static A,
161}
162
163impl<A: 'static + time::Alarm<'static>> VirtualSchedulerTimer<A> {
164    pub fn new(alarm: &'static A) -> Self {
165        Self { alarm }
166    }
167}
168
169impl<A: 'static + time::Alarm<'static>> SchedulerTimer for VirtualSchedulerTimer<A> {
170    fn reset(&self) {
171        let _ = self.alarm.disarm();
172    }
173
174    fn start(&self, us: NonZeroU32) {
175        let tics = {
176            // We need to convert from microseconds to native tics, which could overflow in 32-bit
177            // arithmetic. So we convert to 64-bit. 64-bit division is an expensive subroutine, but
178            // if `us` is a power of 10 the compiler will simplify it with the 1_000_000 divisor
179            // instead.
180            let us = us.get() as u64;
181            let hertz = A::Frequency::frequency() as u64;
182
183            (hertz * us / 1_000_000) as u32
184        };
185
186        let reference = self.alarm.now();
187        self.alarm.set_alarm(reference, A::Ticks::from(tics));
188    }
189
190    fn arm(&self) {
191        //self.alarm.arm();
192    }
193
194    fn disarm(&self) {
195        //self.alarm.disarm();
196    }
197
198    fn get_remaining_us(&self) -> Option<NonZeroU32> {
199        // We need to convert from native tics to us, multiplication could overflow in 32-bit
200        // arithmetic. So we convert to 64-bit.
201
202        let diff = self
203            .alarm
204            .get_alarm()
205            .wrapping_sub(self.alarm.now())
206            .into_u32() as u64;
207
208        // If next alarm is more than one second away from now, alarm must have expired.
209        // Use this formulation to protect against errors when now has passed alarm.
210        // 1 second was chosen because it is significantly greater than the 400ms max value allowed
211        // by start(), and requires no computational overhead (e.g. using 500ms would require
212        // dividing the returned ticks by 2)
213        // However, if the alarm frequency is slow enough relative to the cpu frequency, it is
214        // possible this will be evaluated while now() == get_alarm(), so we special case that
215        // result where the alarm has fired but the subtraction has not overflowed
216        if diff >= A::Frequency::frequency() as u64 {
217            None
218        } else {
219            let hertz = A::Frequency::frequency() as u64;
220            NonZeroU32::new(((diff * 1_000_000) / hertz) as u32)
221        }
222    }
223}