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}