Skip to main content

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);