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 core::num::NonZeroU32;
11
12/// Interface for the system scheduler timer.
13///
14/// A system scheduler timer provides a countdown timer to enforce process
15/// scheduling time quanta. Implementations should have consistent timing while
16/// the CPU is active, but need not operate during sleep. Note, many scheduler
17/// implementations also charge time spent running the kernel on behalf of the
18/// process against the process time quantum.
19///
20/// The primary requirement an implementation of this interface must satisfy is
21/// it must be capable of generating an interrupt when the timer expires. This
22/// interrupt will interrupt the executing process, returning control to the
23/// kernel, and allowing the scheduler to make decisions about what to run next.
24///
25/// On most chips, this interface will be implemented by a core peripheral (e.g.
26/// the ARM core SysTick peripheral). However, some chips lack this optional
27/// peripheral, in which case it might be implemented by another timer or alarm
28/// peripheral, or require virtualization on top of a shared hardware timer.
29///
30/// The `SchedulerTimer` interface is carefully designed to be rather general to
31/// support the various implementations required on different hardware
32/// platforms. The general operation is the kernel will start a timer, which
33/// starts the time quantum assigned to a process. While the process is running,
34/// the kernel will arm the timer, telling the implementation it must ensure
35/// that an interrupt will occur when the time quantum is exhausted. When the
36/// process has stopped running, the kernel will disarm the timer, indicating to
37/// the implementation that an interrupt is no longer required. To check if the
38/// process has exhausted its time quantum the kernel will explicitly ask the
39/// implementation. The kernel itself does not expect to get an interrupt to
40/// handle when the time quantum is exhausted. This is because the time quantum
41/// may end while the kernel itself is running, and the kernel does not need to
42/// effectively preempt itself.
43///
44/// The `arm()` and `disarm()` functions in this interface serve as an optional
45/// optimization opportunity. This pair allows an implementation to only enable
46/// the interrupt when it is strictly necessary, i.e. while the process is
47/// actually executing. However, a correct implementation can have interrupts
48/// enabled anytime the scheduler timer has been started. What the
49/// implementation must ensure is that the interrupt is enabled when `arm()` is
50/// called.
51///
52/// Implementations must take care when using interrupts. Since the
53/// `SchedulerTimer` is used in the core kernel loop and scheduler, top half
54/// interrupt handlers may not have executed before `SchedulerTimer` functions
55/// are called. In particular, implementations on top of virtualized timers may
56/// receive the interrupt fired upcall "late" (i.e. after the kernel calls
57/// `has_expired()`). Implementations should ensure that they can reliably check
58/// for timeslice expirations.
59pub trait SchedulerTimer {
60 /// Start a timer for a process timeslice. The `us` argument is the length
61 /// of the timeslice in microseconds.
62 ///
63 /// This must set a timer for an interval as close as possible to the given
64 /// interval in microseconds. Interrupts do not need to be enabled. However,
65 /// if the implementation cannot separate time keeping from interrupt
66 /// generation, the implementation of `start()` should enable interrupts and
67 /// leave them enabled anytime the timer is active.
68 ///
69 /// Callers can assume at least a 24-bit wide clock. Specific timing is
70 /// dependent on the driving clock. For ARM boards with a dedicated SysTick
71 /// peripheral, increments of 10ms are most accurate thanks to additional
72 /// hardware support for this value. ARM SysTick supports intervals up to
73 /// 400ms.
74 fn start(&self, us: NonZeroU32);
75
76 /// Reset the SchedulerTimer.
77 ///
78 /// This must reset the timer, and can safely disable it and put it in a low
79 /// power state. Calling any function other than `start()` immediately after
80 /// `reset()` is invalid.
81 ///
82 /// Implementations _should_ disable the timer and put it in a lower power
83 /// state. However, not all implementations will be able to guarantee this
84 /// (for example depending on the underlying hardware or if the timer is
85 /// implemented on top of a virtualized timer).
86 fn reset(&self);
87
88 /// Arm the SchedulerTimer timer and ensure an interrupt will be generated.
89 ///
90 /// The timer must already be started by calling `start()`. This function
91 /// guarantees that an interrupt will be generated when the already started
92 /// timer expires. This interrupt will preempt the running userspace
93 /// process.
94 ///
95 /// If the interrupt is already enabled when `arm()` is called, this
96 /// function should be a no-op implementation.
97 fn arm(&self);
98
99 /// Disarm the SchedulerTimer timer indicating an interrupt is no longer
100 /// required.
101 ///
102 /// This does not stop the timer, but indicates to the SchedulerTimer that
103 /// an interrupt is no longer required (i.e. the process is no longer
104 /// executing). By not requiring an interrupt this may allow certain
105 /// implementations to be more efficient by removing the overhead of
106 /// handling the interrupt.
107 ///
108 /// If the implementation cannot disable the interrupt without stopping the
109 /// time keeping mechanism, this function should be a no-op implementation.
110 fn disarm(&self);
111
112 /// Return the number of microseconds remaining in the process's timeslice
113 /// if the timeslice is still active.
114 ///
115 /// If the timeslice is still active, this returns `Some()` with the number
116 /// of microseconds remaining in the timeslice. If the timeslice has
117 /// expired, this returns `None`.
118 ///
119 /// This function may not be called after it has returned `None` (signifying
120 /// the timeslice has expired) for a given timeslice until `start()` is
121 /// called again (to start a new timeslice). If `get_remaining_us()` is
122 /// called again after returning `None` without an intervening call to
123 /// `start()`, the return value is unspecified and implementations may
124 /// return whatever they like.
125 fn get_remaining_us(&self) -> Option<NonZeroU32>;
126}
127
128/// A dummy `SchedulerTimer` implementation in which the timer never expires.
129///
130/// Using this implementation is functional, but will mean the scheduler cannot
131/// interrupt non-yielding processes.
132impl SchedulerTimer for () {
133 fn reset(&self) {}
134
135 fn start(&self, _: NonZeroU32) {}
136
137 fn disarm(&self) {}
138
139 fn arm(&self) {}
140
141 fn get_remaining_us(&self) -> Option<NonZeroU32> {
142 NonZeroU32::new(10000) // choose arbitrary large value
143 }
144}