ove/timer.rs
1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! Software timer for oveRTOS.
8//!
9//! [`Timer`] wraps an RTOS software timer with a safe Rust `fn()` callback.
10//! Timers can be periodic or one-shot and are driven by the RTOS tick.
11
12use core::cell::UnsafeCell;
13use core::mem::MaybeUninit;
14
15use crate::bindings;
16use crate::error::{Error, Result};
17
18// SAFETY (module-wide contract for the `unsafe { bindings::ove_*(...) }` FFI
19// calls below): any handle passed to the C API is non-null and refers to a
20// live RTOS object — wrapper constructors establish validity via
21// `Error::from_code`, and `Drop` (or an explicit `deinit`) is the only place
22// a handle is released. Pointer and slice arguments reference caller-owned
23// memory valid for the duration of the call; the C side copies whatever it
24// retains and does not alias them past return (verified against the
25// signatures in `include/ove/*.h`). Blocks that deviate — `transmute`, raw
26// pointer casts from user data, slice reconstruction via `from_raw_parts`,
27// or storing a callback across the FFI boundary — carry their own
28// `// SAFETY:` comment.
29
30/// Caller-owned storage for a [`Timer`] in zero-heap mode (see
31/// [`crate::MutexStorage`]).
32#[allow(dead_code)]
33pub struct TimerStorage {
34 storage: UnsafeCell<MaybeUninit<bindings::ove_timer_storage_t>>,
35}
36
37impl TimerStorage {
38 /// Zero-initialised storage. `const` so it can initialise a `static`.
39 #[inline]
40 pub const fn new() -> Self {
41 Self {
42 storage: UnsafeCell::new(MaybeUninit::zeroed()),
43 }
44 }
45}
46
47impl Default for TimerStorage {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53// SAFETY: see crate::MutexStorage.
54unsafe impl Sync for TimerStorage {}
55
56/// Software timer with a safe Rust callback.
57///
58/// The timer stores a function pointer and generates an internal trampoline
59/// so the user callback is a plain `fn()` — no `unsafe`, no raw pointers.
60pub struct Timer {
61 handle: bindings::ove_timer_t,
62}
63
64impl Timer {
65 /// Create a new timer via heap allocation (only in heap mode).
66 ///
67 /// - `callback` — safe Rust function called each time the timer fires.
68 /// - `period_ms` — timer period in milliseconds.
69 /// - `one_shot` — if `true`, the timer fires once and stops.
70 #[cfg(not(zero_heap))]
71 pub fn new(callback: fn(), period_ms: u32, one_shot: bool) -> Result<Self> {
72 // Store the callback as the user_data pointer. On platforms where
73 // fn() is pointer-sized this is a direct cast; the trampoline
74 // reconverts it.
75 let user_data = callback as *mut core::ffi::c_void;
76
77 let mut handle: bindings::ove_timer_t = core::ptr::null_mut();
78 let rc = unsafe {
79 bindings::ove_timer_create(
80 &mut handle,
81 Some(Self::trampoline),
82 user_data,
83 period_ms,
84 one_shot as i32,
85 )
86 };
87 Error::from_code(rc)?;
88 Ok(Self { handle })
89 }
90
91 /// Create from caller-provided static storage.
92 ///
93 /// # Safety
94 /// Caller must ensure `storage` outlives the `Timer`.
95 #[cfg(zero_heap)]
96 pub unsafe fn from_static(
97 storage: *mut bindings::ove_timer_storage_t,
98 callback: fn(),
99 period_ms: u32,
100 one_shot: bool,
101 ) -> Result<Self> {
102 let user_data = callback as *mut core::ffi::c_void;
103 let mut handle: bindings::ove_timer_t = core::ptr::null_mut();
104 let rc = unsafe {
105 bindings::ove_timer_init(
106 &mut handle,
107 storage,
108 Some(Self::trampoline),
109 user_data,
110 period_ms,
111 one_shot as i32,
112 )
113 };
114 Error::from_code(rc)?;
115 Ok(Self { handle })
116 }
117
118 /// Mode-agnostic constructor (see [`crate::Mutex::create`]). Heap mode
119 /// ignores `storage`; zero-heap mode backs the timer with it.
120 pub fn create(
121 storage: &'static TimerStorage,
122 callback: fn(),
123 period_ms: u32,
124 one_shot: bool,
125 ) -> Result<Self> {
126 #[cfg(not(zero_heap))]
127 {
128 let _ = storage;
129 Self::new(callback, period_ms, one_shot)
130 }
131 #[cfg(zero_heap)]
132 {
133 let ptr = UnsafeCell::raw_get(&storage.storage).cast();
134 unsafe { Self::from_static(ptr, callback, period_ms, one_shot) }
135 }
136 }
137
138 /// Start the timer, beginning countdown from now.
139 ///
140 /// # Errors
141 /// Returns an error if the underlying RTOS call fails.
142 #[inline]
143 pub fn start(&self) -> Result<()> {
144 let rc = unsafe { bindings::ove_timer_start(self.handle) };
145 Error::from_code(rc)
146 }
147
148 /// Stop the timer, preventing further callbacks until restarted.
149 ///
150 /// # Errors
151 /// Returns an error if the underlying RTOS call fails.
152 #[inline]
153 pub fn stop(&self) -> Result<()> {
154 let rc = unsafe { bindings::ove_timer_stop(self.handle) };
155 Error::from_code(rc)
156 }
157
158 /// Reset the timer, restarting the period from now.
159 ///
160 /// If the timer is stopped, this also starts it.
161 ///
162 /// # Errors
163 /// Returns an error if the underlying RTOS call fails.
164 #[inline]
165 pub fn reset(&self) -> Result<()> {
166 let rc = unsafe { bindings::ove_timer_reset(self.handle) };
167 Error::from_code(rc)
168 }
169
170 /// Internal trampoline that converts the C callback into a safe Rust call.
171 unsafe extern "C" fn trampoline(
172 _timer: bindings::ove_timer_t,
173 user_data: *mut core::ffi::c_void,
174 ) {
175 // SAFETY: `user_data` was stored by `Timer::new`/`from_static` from a
176 // `fn()` pointer. Targets supported by oveRTOS have pointer-sized
177 // function pointers with a C-compatible ABI, so round-tripping
178 // through `*mut c_void` is well-defined.
179 let cb: fn() = unsafe { core::mem::transmute(user_data) };
180 cb();
181 }
182}
183
184crate::ove_handle_impl!(Timer, ove_timer_destroy, ove_timer_deinit);