capsules_system/virtual_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 2025.
4
5//! Implementation of [`SchedulerTimer`] trait on top of a virtual alarm.
6
7use core::num::NonZeroU32;
8use kernel::hil::time::{self, Frequency, Ticks};
9use kernel::platform::scheduler_timer::SchedulerTimer;
10
11/// Implementation of [`SchedulerTimer`] trait on top of a virtual alarm.
12///
13/// Currently, this implementation depends slightly on the virtual alarm
14/// implementation in capsules -- namely it assumes that get_alarm will still
15/// return the passed value even after the timer is disarmed. Thus this should
16/// only be implemented with a virtual alarm. If a dedicated hardware timer is
17/// available, it is more performant to implement the scheduler timer directly
18/// for that hardware peripheral without the alarm abstraction in between.
19///
20/// This mostly handles conversions from wall time, the required inputs to the
21/// trait, to ticks, which are used to track time for alarms.
22pub struct VirtualSchedulerTimer<A: 'static + time::Alarm<'static>> {
23 alarm: &'static A,
24}
25
26impl<A: 'static + time::Alarm<'static>> VirtualSchedulerTimer<A> {
27 pub fn new(alarm: &'static A) -> Self {
28 Self { alarm }
29 }
30}
31
32impl<A: 'static + time::Alarm<'static>> SchedulerTimer for VirtualSchedulerTimer<A> {
33 fn reset(&self) {
34 let _ = self.alarm.disarm();
35 }
36
37 fn start(&self, us: NonZeroU32) {
38 let tics = {
39 // We need to convert from microseconds to native tics, which could overflow in 32-bit
40 // arithmetic. So we convert to 64-bit. 64-bit division is an expensive subroutine, but
41 // if `us` is a power of 10 the compiler will simplify it with the 1_000_000 divisor
42 // instead.
43 let us = us.get() as u64;
44 let hertz = A::Frequency::frequency() as u64;
45
46 (hertz * us / 1_000_000) as u32
47 };
48
49 let reference = self.alarm.now();
50 self.alarm.set_alarm(reference, A::Ticks::from(tics));
51 }
52
53 fn arm(&self) {
54 //self.alarm.arm();
55 }
56
57 fn disarm(&self) {
58 //self.alarm.disarm();
59 }
60
61 fn get_remaining_us(&self) -> Option<NonZeroU32> {
62 // We need to convert from native tics to us, multiplication could overflow in 32-bit
63 // arithmetic. So we convert to 64-bit.
64
65 let diff = self
66 .alarm
67 .get_alarm()
68 .wrapping_sub(self.alarm.now())
69 .into_u32() as u64;
70
71 // If next alarm is more than one second away from now, alarm must have expired.
72 // Use this formulation to protect against errors when now has passed alarm.
73 // 1 second was chosen because it is significantly greater than the 400ms max value allowed
74 // by start(), and requires no computational overhead (e.g. using 500ms would require
75 // dividing the returned ticks by 2)
76 // However, if the alarm frequency is slow enough relative to the cpu frequency, it is
77 // possible this will be evaluated while now() == get_alarm(), so we special case that
78 // result where the alarm has fired but the subtraction has not overflowed
79 if diff >= A::Frequency::frequency() as u64 {
80 None
81 } else {
82 let hertz = A::Frequency::frequency() as u64;
83 NonZeroU32::new(((diff * 1_000_000) / hertz) as u32)
84 }
85 }
86}