veecle_freertos_integration/
timers.rs

1use alloc::boxed::Box;
2use core::ffi::CStr;
3use core::marker::PhantomData;
4use core::ptr;
5
6use veecle_freertos_sys::bindings::{
7    TickType_t, TimerHandle_t, pdFALSE, pdTRUE, pvTimerGetTimerID, shim_xTimerChangePeriod,
8    shim_xTimerDelete, shim_xTimerStart, shim_xTimerStartFromISR, shim_xTimerStop, xTimerCreate,
9    xTimerPendFunctionCall,
10};
11
12use crate::units::Duration;
13use crate::{FreeRtosError, InterruptContext};
14
15/// Wraps the reference to a FreeRTOS's timer handle, exposing an API to safely communicate with FreeRTOS
16/// and perform actions over the corresponding [Timer].
17#[derive(Clone, Copy, Debug)]
18pub struct TimerHandle(TimerHandle_t);
19
20impl TimerHandle {
21    /// Millis to wait for blocking operations.
22    const MS_TIMEOUT: TickType_t = 50;
23
24    /// Start the timer.
25    pub fn start(&self) -> Result<(), FreeRtosError> {
26        // SAFETY:
27        // Our handle is a valid undeleted timer based on the field guarantee.
28        if unsafe { shim_xTimerStart(self.as_ptr(), Self::block_time()) } == pdTRUE() {
29            Ok(())
30        } else {
31            Err(FreeRtosError::Timeout)
32        }
33    }
34
35    /// Start the timer from an interrupt.
36    pub fn start_from_isr(&self, context: &mut InterruptContext) -> Result<(), FreeRtosError> {
37        // SAFETY:
38        // Our handle is a valid undeleted timer based on the field guarantee.
39        if unsafe { shim_xTimerStartFromISR(self.as_ptr(), context.get_task_field_mut()) }
40            == pdTRUE()
41        {
42            Ok(())
43        } else {
44            Err(FreeRtosError::QueueSendTimeout)
45        }
46    }
47
48    /// Stop the timer.
49    pub fn stop(&self) -> Result<(), FreeRtosError> {
50        // SAFETY:
51        // Our handle is a valid undeleted timer based on the field guarantee.
52        if unsafe { shim_xTimerStop(self.as_ptr(), Self::block_time()) } == pdTRUE() {
53            Ok(())
54        } else {
55            Err(FreeRtosError::Timeout)
56        }
57    }
58
59    /// Change the period of the timer.
60    pub fn change_period(&self, new_period: Duration) -> Result<(), FreeRtosError> {
61        if new_period.ticks() == 0 {
62            return Err(FreeRtosError::ZeroDuration);
63        }
64        // SAFETY:
65        // Our handle is a valid undeleted timer based on the field guarantee. This call is unreachable if `new_period`
66        // equals zero.
67        if unsafe { shim_xTimerChangePeriod(self.as_ptr(), new_period.ticks(), Self::block_time()) }
68            == pdTRUE()
69        {
70            Ok(())
71        } else {
72            Err(FreeRtosError::Timeout)
73        }
74    }
75
76    #[inline]
77    fn as_ptr(&self) -> TimerHandle_t {
78        self.0
79    }
80
81    /// Returns the timer id.
82    fn id(&self) -> *mut core::ffi::c_void {
83        // SAFETY:
84        // Our handle is a valid undeleted timer based on the field guarantee.
85        unsafe { pvTimerGetTimerID(self.as_ptr()) }
86    }
87
88    /// Helper that returns the block time in ticks.
89    #[inline]
90    fn block_time() -> TickType_t {
91        Duration::from_ms(Self::MS_TIMEOUT).ticks()
92    }
93}
94
95/// A FreeRTOS software timer.
96///
97/// Note that all operations on a timer are processed by a FreeRTOS internal task
98/// that receives messages in a queue. Every operation has an associated waiting time
99/// for that queue to get unblocked.
100#[derive(Debug)]
101pub struct Timer<F>
102where
103    F: Fn(TimerHandle) + Send + 'static,
104{
105    handle: TimerHandle,
106    callback: PhantomData<F>,
107}
108
109impl<F> Timer<F>
110where
111    F: Fn(TimerHandle) + Send + 'static,
112{
113    /// Creates a new [`Timer`] which ticks periodically.
114    pub fn periodic(
115        name: Option<&'static CStr>,
116        period: Duration,
117        callback: F,
118    ) -> Result<Self, FreeRtosError> {
119        Self::spawn(name, period.ticks(), true, callback)
120    }
121
122    /// Creates a [`Timer`] that ticks once.
123    pub fn once(
124        name: Option<&'static CStr>,
125        period: Duration,
126        callback: F,
127    ) -> Result<Self, FreeRtosError> {
128        Self::spawn(name, period.ticks(), false, callback)
129    }
130
131    /// Returns the [`TimerHandle`] of self.
132    pub fn handle(&self) -> TimerHandle {
133        self.handle
134    }
135
136    /// Detach this timer from Rust's memory management. The timer will still be active and
137    /// will consume the memory.
138    ///
139    /// Can be used for timers that will never be changed and don't need to stay in scope.
140    ///
141    /// This method is safe because resource leak is safe in Rust.
142    pub fn detach(self) {
143        core::mem::forget(self);
144    }
145
146    /// Tries to create a timer with the given strategy.
147    fn spawn(
148        name: Option<&'static CStr>,
149        period: TickType_t,
150        auto_reload: bool,
151        callback: F,
152    ) -> Result<Self, FreeRtosError> {
153        if period == 0 {
154            return Err(FreeRtosError::ZeroDuration);
155        }
156
157        let callback = Box::into_raw(Box::new(callback));
158
159        let name = name.map_or(ptr::null(), |name| name.as_ptr());
160
161        // SAFETY:
162        // The content of `name` is an static `CStr`, ensuring it exists for the whole life of the program.
163        // This code is unreachable if period equals zero.
164        // The callback whose pointer is used as `pvTimerID` is owned by the timer being constructed, ensuring its
165        // correctness for the whole lifetime of this timer.
166        let handle = unsafe {
167            xTimerCreate(
168                name.cast(),
169                period,
170                if auto_reload { pdTRUE() } else { pdFALSE() },
171                callback.cast(),
172                Some(Self::callback_bridge),
173            )
174        };
175
176        if handle.is_null() {
177            // SAFETY: Creating the timer failed so we retain ownership of the callback.
178            drop(unsafe { Box::from_raw(callback) });
179            return Err(FreeRtosError::OutOfMemory);
180        }
181
182        Ok(Self {
183            handle: TimerHandle(handle),
184            callback: PhantomData,
185        })
186    }
187
188    /// A callback for FreeRTOS's software timers.
189    ///
190    /// This method behaves like a bridge between the FreeRTOS API and the callback given by the final user.
191    extern "C" fn callback_bridge(handle: TimerHandle_t) {
192        let handle = TimerHandle(handle);
193        // SAFETY:
194        // The callback's pointer is obtained during spawn and assigned as the timer's id, therefore the
195        // pointer belongs to a valid callback.
196        let callback =
197            unsafe { handle.id().cast::<F>().as_ref() }.expect("callback should not be null");
198        callback(handle);
199    }
200
201    /// Drops the timer's callback.
202    extern "C" fn drop_callback(timer_id: *mut core::ffi::c_void, _: u32) {
203        let callback = timer_id.cast::<F>();
204        assert!(!callback.is_null(), "callback pointer is null");
205
206        // SAFETY:
207        // The callback's pointer is obtained during spawn and assigned as the timer's id, therefore the
208        // pointer belongs to a valid callback, which is ensured to not be null.
209        // Also, since the timer has previously been deleted, which is the only task in charge of calling
210        // `callback_bridge`, which, ultimately, would call the callback being dropped, it is sure that timer
211        // handle will never be called again and attempt to access the callback.
212        drop(unsafe { Box::from_raw(callback) });
213    }
214}
215
216impl<F> Drop for Timer<F>
217where
218    F: Fn(TimerHandle) + Send + 'static,
219{
220    fn drop(&mut self) {
221        // get timer's id before deleting it.
222        let timer_id = self.handle.id();
223
224        // SAFETY:
225        // The timer's handle is always initialized during spawn, and it is not possible to create a timer
226        // without spawning it.
227        let result = unsafe { shim_xTimerDelete(self.handle.as_ptr(), TimerHandle::block_time()) };
228
229        assert_eq!(result, pdTRUE(), "timer deletion has failed");
230
231        // SAFETY:
232        // The handle from which the value of `pvParameter1` is obtained is created during the construction of self,
233        // ensuring it is still valid during the execution of `drop_callback`.
234        let result = unsafe {
235            xTimerPendFunctionCall(
236                Some(Self::drop_callback),
237                timer_id,
238                0,
239                TimerHandle::block_time(),
240            )
241        };
242
243        assert_eq!(result, pdTRUE(), "drop callback scheduling has failed");
244    }
245}