ove/
thread.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//! RTOS thread management for oveRTOS.
8//!
9//! [`Thread`] wraps an RTOS task handle with RAII lifecycle management. Threads
10//! can be created from safe Rust `fn()` entry points via [`Thread::spawn`], and
11//! are automatically destroyed when the [`Thread`] handle is dropped.
12
13use core::fmt;
14
15use crate::bindings;
16use crate::error::{Error, Result};
17use crate::priority::Priority;
18
19/// Thread state, matching `ove_thread_state_t`.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ThreadState {
22    /// The thread is currently executing on a CPU core.
23    Running,
24    /// The thread is ready to run but waiting for a CPU slot.
25    Ready,
26    /// The thread is waiting on a synchronization primitive or I/O.
27    Blocked,
28    /// The thread has been explicitly suspended via [`Thread::suspend`].
29    Suspended,
30    /// The thread has finished executing.
31    Terminated,
32    /// The state reported by the RTOS does not match any known variant.
33    Unknown,
34}
35
36/// Runtime statistics for a thread.
37#[derive(Debug, Clone, Copy)]
38pub struct ThreadStats {
39    /// Total CPU time consumed by this thread in microseconds.
40    pub runtime_us: u64,
41    /// CPU utilization as a percentage multiplied by 100 (e.g. 5025 = 50.25%).
42    pub cpu_percent_x100: u32,
43}
44
45/// Handle to an OS thread.
46///
47/// For the common case of current-thread operations (`sleep_ms`, `yield_now`),
48/// use the associated functions directly — no handle needed.
49pub struct Thread {
50    handle: bindings::ove_thread_t,
51    owned: bool,
52}
53
54impl Thread {
55    /// Sleep the current thread for `ms` milliseconds.
56    pub fn sleep_ms(ms: u32) {
57        unsafe { bindings::ove_thread_sleep_ms(ms) }
58    }
59
60    /// Yield the current thread's time slice.
61    pub fn yield_now() {
62        unsafe { bindings::ove_thread_yield() }
63    }
64
65    /// Get a non-owning handle to the currently running thread.
66    ///
67    /// The returned handle will **not** destroy the thread on drop,
68    /// since the caller does not own it.
69    pub fn current() -> Self {
70        let handle = unsafe { bindings::ove_thread_get_self() };
71        Self { handle, owned: false }
72    }
73
74    /// Create a new thread via heap allocation (only in heap mode).
75    ///
76    /// This is the low-level API that takes an `unsafe extern "C"` entry.
77    /// Prefer [`Thread::spawn()`] for safe Rust entry functions.
78    ///
79    /// # Errors
80    /// Returns [`Error::NoMemory`] if heap allocation fails, or another error
81    /// if the RTOS rejects the thread descriptor.
82    #[cfg(not(zero_heap))]
83    pub fn create(
84        name: &[u8],
85        entry: unsafe extern "C" fn(*mut core::ffi::c_void),
86        priority: Priority,
87        stack_size: usize,
88    ) -> Result<Self> {
89        let desc = bindings::ove_thread_desc {
90            name: name.as_ptr() as *const _,
91            entry: Some(entry),
92            arg: core::ptr::null_mut(),
93            priority: priority as bindings::ove_prio_t,
94            stack_size,
95            stack: core::ptr::null_mut(),
96        };
97        let mut handle: bindings::ove_thread_t = core::ptr::null_mut();
98        let rc = unsafe { bindings::ove_thread_create_(&mut handle, &desc) };
99        Error::from_code(rc)?;
100        Ok(Self { handle, owned: true })
101    }
102
103    /// Spawn a thread with a safe Rust entry function.
104    ///
105    /// Uses a trampoline to convert `fn()` into the `unsafe extern "C"` entry
106    /// expected by the C API. No raw pointers or `unsafe` needed in user code.
107    ///
108    /// # Errors
109    /// Returns [`Error::NoMemory`] if heap allocation fails, or another error
110    /// if the RTOS rejects the thread descriptor.
111    #[cfg(not(zero_heap))]
112    pub fn spawn(
113        name: &[u8],
114        entry: fn(),
115        priority: Priority,
116        stack_size: usize,
117    ) -> Result<Self> {
118        unsafe extern "C" fn trampoline(arg: *mut core::ffi::c_void) {
119            let entry: fn() = unsafe { core::mem::transmute(arg) };
120            entry();
121        }
122
123        let desc = bindings::ove_thread_desc {
124            name: name.as_ptr() as *const _,
125            entry: Some(trampoline),
126            arg: entry as *mut core::ffi::c_void,
127            priority: priority as bindings::ove_prio_t,
128            stack_size,
129            stack: core::ptr::null_mut(),
130        };
131        let mut handle: bindings::ove_thread_t = core::ptr::null_mut();
132        let rc = unsafe { bindings::ove_thread_create_(&mut handle, &desc) };
133        Error::from_code(rc)?;
134        Ok(Self { handle, owned: true })
135    }
136
137    /// Create from caller-provided static storage.
138    ///
139    /// The `desc` must include a valid `stack` pointer and `stack_size`.
140    ///
141    /// # Safety
142    /// - `storage` must outlive the `Thread` and not be shared.
143    /// - The stack buffer referenced by `desc` must outlive the `Thread`.
144    #[cfg(zero_heap)]
145    pub unsafe fn from_static(
146        storage: *mut bindings::ove_thread_storage_t,
147        desc: &bindings::ove_thread_desc,
148    ) -> Result<Self> {
149        let mut handle: bindings::ove_thread_t = core::ptr::null_mut();
150        let rc = unsafe { bindings::ove_thread_init(&mut handle, storage, desc) };
151        Error::from_code(rc)?;
152        Ok(Self { handle, owned: true })
153    }
154
155    /// Spawn a thread with a safe Rust entry using static storage.
156    ///
157    /// Zero-heap variant of [`Thread::spawn()`] — takes caller-provided
158    /// storage and stack but still uses the safe trampoline pattern.
159    ///
160    /// # Safety
161    /// - `storage` must outlive the `Thread` and not be shared.
162    /// - `stack` must point to at least `stack_size` bytes and outlive the `Thread`.
163    #[cfg(zero_heap)]
164    pub unsafe fn spawn_static(
165        storage: *mut bindings::ove_thread_storage_t,
166        stack: *mut core::ffi::c_void,
167        name: &[u8],
168        entry: fn(),
169        priority: Priority,
170        stack_size: usize,
171    ) -> Result<Self> {
172        unsafe extern "C" fn trampoline(arg: *mut core::ffi::c_void) {
173            let entry: fn() = unsafe { core::mem::transmute(arg) };
174            entry();
175        }
176
177        let desc = bindings::ove_thread_desc {
178            name: name.as_ptr() as *const _,
179            entry: Some(trampoline),
180            arg: entry as *mut core::ffi::c_void,
181            priority: priority as bindings::ove_prio_t,
182            stack_size,
183            stack,
184        };
185        let mut handle: bindings::ove_thread_t = core::ptr::null_mut();
186        let rc = unsafe { bindings::ove_thread_init(&mut handle, storage, &desc) };
187        Error::from_code(rc)?;
188        Ok(Self { handle, owned: true })
189    }
190
191    /// Suspend this thread.
192    pub fn suspend(&self) {
193        unsafe { bindings::ove_thread_suspend(self.handle) }
194    }
195
196    /// Resume this thread.
197    pub fn resume(&self) {
198        unsafe { bindings::ove_thread_resume(self.handle) }
199    }
200
201    /// Set this thread's priority.
202    pub fn set_priority(&self, prio: Priority) {
203        unsafe { bindings::ove_thread_set_priority(self.handle, prio as bindings::ove_prio_t) }
204    }
205
206    /// Get current stack usage in bytes.
207    pub fn get_stack_usage(&self) -> usize {
208        unsafe { bindings::ove_thread_get_stack_usage(self.handle) }
209    }
210
211    /// Get the thread's current state.
212    pub fn get_state(&self) -> ThreadState {
213        let state = unsafe { bindings::ove_thread_get_state(self.handle) };
214        match state {
215            bindings::OVE_THREAD_STATE_RUNNING => ThreadState::Running,
216            bindings::OVE_THREAD_STATE_READY => ThreadState::Ready,
217            bindings::OVE_THREAD_STATE_BLOCKED => ThreadState::Blocked,
218            bindings::OVE_THREAD_STATE_SUSPENDED => {
219                ThreadState::Suspended
220            }
221            bindings::OVE_THREAD_STATE_TERMINATED => {
222                ThreadState::Terminated
223            }
224            _ => ThreadState::Unknown,
225        }
226    }
227
228    /// Get runtime statistics (CPU time and utilization) for this thread.
229    ///
230    /// # Errors
231    /// Returns an error if the RTOS does not support runtime statistics or the
232    /// thread handle is invalid.
233    pub fn get_runtime_stats(&self) -> Result<ThreadStats> {
234        let mut stats = bindings::ove_thread_stats {
235            runtime_us: 0,
236            cpu_percent_x100: 0,
237        };
238        let rc =
239            unsafe { bindings::ove_thread_get_runtime_stats(self.handle, &mut stats) };
240        Error::from_code(rc)?;
241        Ok(ThreadStats {
242            runtime_us: stats.runtime_us,
243            cpu_percent_x100: stats.cpu_percent_x100,
244        })
245    }
246}
247
248impl fmt::Debug for Thread {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        f.debug_struct("Thread")
251            .field("handle", &format_args!("{:p}", self.handle))
252            .field("owned", &self.owned)
253            .finish()
254    }
255}
256
257// ---------------------------------------------------------------------------
258// System heap statistics
259// ---------------------------------------------------------------------------
260
261/// System heap statistics.
262#[derive(Debug, Clone, Copy)]
263pub struct MemStats {
264    /// Total heap size in bytes.
265    pub total: usize,
266    /// Current free heap in bytes.
267    pub free: usize,
268    /// Current used heap in bytes.
269    pub used: usize,
270    /// High-water-mark usage in bytes.
271    pub peak_used: usize,
272}
273
274/// Query system heap statistics.
275///
276/// # Errors
277/// Returns an error if the RTOS does not support heap statistics.
278pub fn get_mem_stats() -> Result<MemStats> {
279    let mut raw: bindings::ove_mem_stats = unsafe { core::mem::zeroed() };
280    let rc = unsafe { bindings::ove_sys_get_mem_stats(&mut raw) };
281    Error::from_code(rc)?;
282    Ok(MemStats {
283        total: raw.total,
284        free: raw.free,
285        used: raw.used,
286        peak_used: raw.peak_used,
287    })
288}
289
290// ---------------------------------------------------------------------------
291// Thread enumeration
292// ---------------------------------------------------------------------------
293
294/// Snapshot of a single thread's info.
295#[derive(Debug, Clone, Copy)]
296pub struct ThreadInfo {
297    /// Thread name (static string from RTOS).
298    pub name: &'static [u8],
299    /// Execution state.
300    pub state: bindings::ove_thread_state_t,
301    /// Priority level.
302    pub priority: i32,
303    /// Stack high-water mark in bytes.
304    pub stack_used: usize,
305}
306
307/// List all threads in the system.
308///
309/// Fills the provided buffer with thread info snapshots and returns the
310/// slice of entries actually written.
311///
312/// # Errors
313/// Returns an error if the RTOS does not support thread enumeration.
314pub fn thread_list(buf: &mut [ThreadInfo]) -> Result<&[ThreadInfo]> {
315    const MAX_THREADS: usize = 32;
316    let count = buf.len().min(MAX_THREADS);
317    let mut raw: [bindings::ove_thread_info; MAX_THREADS] = unsafe { core::mem::zeroed() };
318    let mut actual: usize = 0;
319    let rc = unsafe { bindings::ove_thread_list(raw.as_mut_ptr(), count, &mut actual) };
320    Error::from_code(rc)?;
321
322    let actual = actual.min(count);
323    for i in 0..actual {
324        let name = if raw[i].name.is_null() {
325            &[]
326        } else {
327            unsafe {
328                let p = raw[i].name as *const u8;
329                let mut len = 0;
330                while *p.add(len) != 0 {
331                    len += 1;
332                }
333                core::slice::from_raw_parts(p, len)
334            }
335        };
336        buf[i] = ThreadInfo {
337            name,
338            state: raw[i].state,
339            priority: raw[i].priority,
340            stack_used: raw[i].stack_used,
341        };
342    }
343    Ok(&buf[..actual])
344}
345
346impl Drop for Thread {
347    fn drop(&mut self) {
348        if self.owned && !self.handle.is_null() {
349            #[cfg(not(zero_heap))]
350            unsafe { bindings::ove_thread_destroy(self.handle) };
351            #[cfg(zero_heap)]
352            unsafe { bindings::ove_thread_deinit(self.handle) };
353        }
354    }
355}