veecle_osal_freertos/
time.rs

1//! Time related system utilities.
2
3use alloc::boxed::Box;
4use alloc::sync::Arc;
5use core::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
6use core::task::Poll;
7
8use futures::task::AtomicWaker;
9use veecle_freertos_integration::{
10    Duration as FreeRtosDuration, TickType_t, Timer as FreeRtosTimer,
11};
12use veecle_osal_api::Error;
13pub use veecle_osal_api::time::{
14    Duration, Exceeded, Instant, Interval, SystemTime, SystemTimeError, SystemTimeSync,
15    TimeAbstraction,
16};
17
18use crate::error::into_veecle_os_error;
19
20/// Implements the [`TimeAbstraction`] trait for FreeRTOS.
21///
22/// ## Details
23///
24/// - Before using [`Self::duration_since_epoch`], you are expected to synchronize system time via
25///   [`Self::set_system_time`].
26/// - Maximum precision of system time synchronization is limited to seconds.
27#[derive(Debug)]
28pub struct Time;
29
30impl TimeAbstraction for Time {
31    fn now() -> Instant {
32        Instant::MIN
33            + Duration::from_millis(
34                veecle_freertos_integration::scheduler::get_tick_count_duration().ms() as u64,
35            )
36    }
37
38    async fn sleep(duration: Duration) -> Result<(), Error> {
39        let duration = FreeRtosDuration::from_ms(duration.as_millis() as TickType_t);
40
41        if duration == FreeRtosDuration::zero() {
42            return Ok(());
43        }
44
45        /// Data shared between the timer managed by FreeRTOS and the future we're returning.
46        #[derive(Default)]
47        struct Context {
48            /// Tracks the waker currently associated to the future.
49            waker: AtomicWaker,
50
51            /// Set by the timer once the deadline is hit, to avoid spurious wakeups resolving early.
52            done: AtomicBool,
53        }
54
55        let context = Arc::new(Context::default());
56
57        let callback = Box::new({
58            let context = context.clone();
59            move |_| {
60                context.done.store(true, Ordering::Relaxed);
61                context.waker.wake();
62            }
63        });
64
65        let timer =
66            FreeRtosTimer::periodic(None, duration, callback).map_err(into_veecle_os_error)?;
67
68        timer.handle().start().map_err(into_veecle_os_error)?;
69
70        core::future::poll_fn(move |cx| {
71            context.waker.register(cx.waker());
72            if context.done.load(Ordering::Relaxed) {
73                Poll::Ready(())
74            } else {
75                Poll::Pending
76            }
77        })
78        .await;
79
80        Ok(())
81    }
82
83    async fn sleep_until(deadline: Instant) -> Result<(), Error> {
84        Self::sleep(
85            deadline
86                .duration_since(Self::now())
87                .unwrap_or(Duration::ZERO),
88        )
89        .await
90    }
91
92    fn interval(period: Duration) -> impl Interval
93    where
94        Self: Sized,
95    {
96        /// Data shared between the timer managed by FreeRTOS and the stream we're returning.
97        struct Context {
98            /// Tracks the waker currently associated to the stream.
99            waker: AtomicWaker,
100
101            /// How many more times the timer has fired than the stream has yielded.
102            ///
103            /// This is kept as a count so that when the stream misses ticks it will obey the trait docs.
104            count: AtomicUsize,
105        }
106
107        struct IntervalInternal<F>
108        where
109            F: Fn(veecle_freertos_integration::TimerHandle) + Send + 'static,
110        {
111            // Owned but unused so it gets dropped and de-registered when this is dropped.
112            _timer: Option<FreeRtosTimer<F>>,
113            error: Option<veecle_freertos_integration::FreeRtosError>,
114            context: Arc<Context>,
115        }
116
117        impl<F> Interval for IntervalInternal<F>
118        where
119            F: Fn(veecle_freertos_integration::TimerHandle) + Send + 'static,
120        {
121            async fn tick(&mut self) -> Result<(), Error> {
122                if let Some(error) = self.error.take() {
123                    return Err(into_veecle_os_error(error));
124                }
125
126                core::future::poll_fn(|cx| {
127                    self.context.waker.register(cx.waker());
128
129                    // It's ok to split the load and sub here because this is the only place that subtracts, we want to
130                    // saturate at 0 rather than wrapping over to `usize::MAX`, and we don't care about the exact count.
131                    if self.context.count.load(Ordering::Relaxed) != 0 {
132                        self.context.count.fetch_sub(1, Ordering::Relaxed);
133                        Poll::Ready(Ok(()))
134                    } else {
135                        Poll::Pending
136                    }
137                })
138                .await
139            }
140        }
141
142        let period = FreeRtosDuration::from_ms(period.as_millis() as TickType_t);
143
144        let context = Arc::new(Context {
145            waker: AtomicWaker::new(),
146            count: AtomicUsize::new(1),
147        });
148
149        let callback = Box::new({
150            let context = context.clone();
151            move |_| {
152                // This could technically wrap around, but at that point with a 1 millisecond period the stream is
153                // already behind by 49 days and I don't think we care.
154                context.count.fetch_add(1, Ordering::Relaxed);
155                context.waker.wake();
156            }
157        });
158
159        let (mut error, timer) = match FreeRtosTimer::periodic(None, period, callback) {
160            Ok(timer) => (None, Some(timer)),
161            Err(error) => (Some(error), None),
162        };
163
164        if let Some(timer) = &timer {
165            error = timer.handle().start().err();
166        }
167
168        IntervalInternal {
169            _timer: timer,
170            error,
171            context,
172        }
173    }
174}
175
176static SYSTEM_TIME_OFFSET_SECONDS: AtomicU32 = AtomicU32::new(0);
177
178impl SystemTime for Time {
179    fn duration_since_epoch() -> Result<Duration, SystemTimeError> {
180        let offset = SYSTEM_TIME_OFFSET_SECONDS.load(Ordering::Relaxed);
181        if offset == 0 {
182            Err(SystemTimeError::Unsynchronized)
183        } else {
184            let duration_since_start = Self::now()
185                .duration_since(Instant::MIN)
186                .expect("now can't be less than min time");
187            Ok(duration_since_start + Duration::from_secs(offset as u64))
188        }
189    }
190}
191
192impl SystemTimeSync for Time {
193    fn set_system_time(duration_since_epoch: Duration) -> Result<(), SystemTimeError> {
194        let duration_since_start = Duration::from_millis(
195            veecle_freertos_integration::scheduler::get_tick_count_duration().ms() as u64,
196        );
197        if duration_since_epoch < duration_since_start {
198            Err(SystemTimeError::EpochIsLaterThanStartTime)
199        } else {
200            SYSTEM_TIME_OFFSET_SECONDS.store(
201                (duration_since_epoch - duration_since_start).as_secs() as u32,
202                Ordering::Relaxed,
203            );
204            Ok(())
205        }
206    }
207}