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}