Skip to main content

osal_rs/posix/
system.rs

1/***************************************************************************
2 *
3 * osal-rs
4 * Copyright (C) 2026 Antonio Salsi <passy.linux@zresa.it>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
18 *
19 ***************************************************************************/
20
21//! System-level control and timing for POSIX.
22//!
23//! [`System`] provides the scheduler-adjacent operations that don't belong
24//! to any single primitive: starting/stopping the "run loop", timing
25//! (`CLOCK_MONOTONIC`-based), and querying/suspending the threads spawned
26//! through this crate's [`crate::os::Thread`] API. Unlike FreeRTOS, POSIX has
27//! no real scheduler to hand control to, so [`System::start`] just spins
28//! until [`System::stop`] is called from another thread.
29//!
30//! # Examples
31//!
32//! ```
33//! use osal_rs::os::*;
34//! use std::sync::Arc;
35//!
36//! // Something else must call `System::stop()` for `start()` to return.
37//! let mut stopper = Thread::new("stopper", 1024, 1);
38//! stopper.spawn_simple(|| {
39//!     System::delay(10);
40//!     System::stop();
41//!     Ok(Arc::new(()))
42//! }).unwrap();
43//!
44//! System::start(); // blocks here until `stop()` runs above
45//! ```
46
47use core::ffi::c_long;
48use core::ops::Deref;
49use core::time::Duration;
50use std::sync::atomic::{AtomicBool, Ordering};
51
52use alloc::vec::Vec;
53
54use crate::os::ThreadFn;
55use crate::posix::ffi::{
56    CLOCK_MONOTONIC, PTHREAD_ONCE_INIT, _SC_AVPHYS_PAGES, _SC_PAGESIZE, clock_gettime, nanosleep, pthread_once, pthread_once_t, pthread_self, sched_yield, sysconf, timespec,
57};
58use crate::posix::thread::{Thread, all_registered_threads, registered_thread_count};
59use crate::posix::types::{BaseType, TickType, UBaseType};
60use crate::traits::{SystemFn, ThreadMetadata, ThreadState, ToTick};
61use crate::utils::OsalRsBool;
62
63static RUN: AtomicBool = AtomicBool::new(true);
64
65/// Snapshot returned by [`System::get_all_thread`]: every thread spawned
66/// through this crate's [`crate::os::Thread`] API (plus the calling thread
67/// itself), and the total elapsed run time at the moment of the snapshot.
68/// Derefs to `&[ThreadMetadata]` for convenient iteration.
69///
70/// # Examples
71///
72/// ```
73/// use osal_rs::os::*;
74///
75/// let state = System::get_all_thread();
76/// // The calling thread is always included.
77/// assert!(!state.is_empty());
78/// ```
79#[derive(Debug, Clone)]
80pub struct SystemState {
81    /// Metadata for every thread spawned through this crate's
82    /// [`crate::os::Thread`] API, plus the calling thread itself.
83    pub tasks: Vec<ThreadMetadata>,
84    /// Total elapsed run time, in milliseconds, at the moment of the
85    /// snapshot (see [`System::get_tick_count`]).
86    pub total_run_time: u32,
87}
88
89impl Deref for SystemState {
90    type Target = [ThreadMetadata];
91
92    fn deref(&self) -> &Self::Target {
93        &self.tasks
94    }
95}
96
97/// Namespace for system-level operations (scheduler control, timing, thread
98/// introspection) - see the [module docs](self) for an overview and a
99/// runnable example. Zero-sized: never instantiated, only used as
100/// `System::function(...)`.
101pub struct System;
102
103impl System {
104    /// Blocks like [`System::delay`], but accepts any [`ToTick`] duration
105    /// (e.g. a [`core::time::Duration`]) instead of a raw tick count.
106    ///
107    /// # Examples
108    ///
109    /// ```
110    /// use osal_rs::os::*;
111    /// use core::time::Duration;
112    ///
113    /// let before = System::get_tick_count();
114    /// System::delay_with_to_tick(Duration::from_millis(10));
115    /// assert!(System::get_tick_count() >= before);
116    /// ```
117    #[inline]
118    pub fn delay_with_to_tick(ticks: impl ToTick) {
119        Self::delay(ticks.to_ticks());
120    }
121
122    /// Blocks like [`System::delay_until`], but accepts any [`ToTick`]
123    /// increment (e.g. a [`core::time::Duration`]) instead of a raw tick
124    /// count.
125    ///
126    /// # Examples
127    ///
128    /// ```
129    /// use osal_rs::os::*;
130    /// use core::time::Duration;
131    ///
132    /// let mut previous = System::get_tick_count();
133    /// System::delay_until_with_to_tick(&mut previous, Duration::from_millis(5));
134    /// ```
135    #[inline]
136    pub fn delay_until_with_to_tick(previous_wake_time: &mut TickType, time_increment: impl ToTick) {
137        Self::delay_until(previous_wake_time, time_increment.to_ticks());
138    }
139
140    fn monotonic_now() -> Duration {
141        let mut ts = timespec::default();
142        unsafe { clock_gettime(CLOCK_MONOTONIC, &mut ts) };
143
144        Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32)
145    }
146
147    fn start_time() -> Duration {
148        static mut ONCE: pthread_once_t = PTHREAD_ONCE_INIT;
149        static mut START_TIME: Duration = Duration::ZERO;
150
151        extern "C" fn init() {
152            unsafe {
153                START_TIME = System::monotonic_now();
154            }
155            // Without this, a caller landing within nanoseconds of the
156            // epoch capture above would measure 0 elapsed ms (millisecond
157            // resolution) on its very first read, since `pthread_once`
158            // blocks every other racing caller until `init` returns.
159            // Paid once per process, lazily, only if timing is ever used.
160            System::delay(1);
161        }
162
163        unsafe {
164            pthread_once(&raw mut ONCE, Some(init));
165            START_TIME
166        }
167    }
168
169    fn elapsed() -> Duration {
170        // `start_time()` must be resolved *before* sampling the clock: on the
171        // very first call it lazily captures the epoch (and burns a tick, see
172        // `init`), so sampling first would subtract a later epoch from an
173        // earlier reading and saturate to zero.
174        let start = Self::start_time();
175        Self::monotonic_now().checked_sub(start).unwrap_or_default()
176    }
177}
178
179impl SystemFn for System {
180    /// Spins until [`System::stop`] is called from another thread. There is
181    /// no real scheduler on POSIX to hand control to, so this is just a busy
182    /// loop over an atomic flag - unlike FreeRTOS, where the equivalent call
183    /// never returns.
184    ///
185    /// # Examples
186    ///
187    /// ```
188    /// use osal_rs::os::*;
189    /// use std::sync::Arc;
190    ///
191    /// let mut stopper = Thread::new("stopper", 1024, 1);
192    /// stopper.spawn_simple(|| {
193    ///     System::delay(10);
194    ///     System::stop();
195    ///     Ok(Arc::new(()))
196    /// }).unwrap();
197    ///
198    /// System::start(); // blocks here until `stop()` runs above
199    /// ```
200    fn start() {
201        loop {
202            if !RUN.load(Ordering::Acquire) {
203                break;
204            }
205            System::delay_with_to_tick(Duration::from_millis(500));
206        }
207    }
208
209    /// Suspends every currently `Ready`/`Running` thread spawned through
210    /// this crate's [`crate::os::Thread`] API (see
211    /// [`crate::os::ThreadFn::suspend`]).
212    ///
213    /// # Examples
214    ///
215    /// ```
216    /// use osal_rs::os::*;
217    /// use std::sync::Arc;
218    ///
219    /// let mut worker = Thread::new("worker", 1024, 1);
220    /// worker.spawn_simple(|| {
221    ///     System::delay(200);
222    ///     Ok(Arc::new(()))
223    /// }).unwrap();
224    ///
225    /// System::delay(10); // give it a moment to start running
226    /// System::suspend_all();
227    /// assert!(System::resume_all() >= 1);
228    /// ```
229    fn suspend_all() {
230        for tm in all_registered_threads() {
231            if let Ok(t) = Thread::new_with_handle(tm.thread, tm.name.as_str(), tm.stack_depth, tm.current_priority) {
232                if tm.state == ThreadState::Ready || tm.state == ThreadState::Running {
233                    t.suspend();
234                }
235            }
236        }
237    }
238
239    /// Resumes every currently `Suspended` thread spawned through this
240    /// crate's [`crate::os::Thread`] API, returning how many were resumed.
241    ///
242    /// See [`System::suspend_all`] for a complete example.
243    fn resume_all() -> BaseType {
244        let mut count = 0;
245
246        for tm in all_registered_threads() {
247            if let Ok(t) = Thread::new_with_handle(tm.thread, tm.name.as_str(), tm.stack_depth, tm.current_priority) {
248                if tm.state == ThreadState::Suspended {
249                    t.resume();
250                    count += 1;
251                }
252            }
253        }
254
255        count
256    }
257
258    /// Signals [`System::start`]'s spin loop to return. See
259    /// [`System::start`] for a complete example.
260    fn stop() {
261        RUN.store(false, Ordering::Release);
262    }
263
264    /// Returns the number of ticks elapsed since the first time any of
265    /// [`System::get_tick_count`]/[`System::get_current_time`] was called
266    /// in this process (that first call defines tick `0`).
267    ///
268    /// # Examples
269    ///
270    /// ```
271    /// use osal_rs::os::*;
272    ///
273    /// let before = System::get_tick_count();
274    /// System::delay(5);
275    /// assert!(System::get_tick_count() >= before);
276    /// ```
277    fn get_tick_count() -> TickType {
278        Self::elapsed().as_millis().min(TickType::MAX as u128) as TickType
279    }
280
281    /// Same reference point as [`System::get_tick_count`], but returned as a
282    /// [`Duration`] instead of a raw tick count.
283    ///
284    /// # Examples
285    ///
286    /// ```
287    /// use osal_rs::os::*;
288    ///
289    /// let before = System::get_current_time();
290    /// System::delay(5);
291    /// assert!(System::get_current_time() >= before);
292    /// ```
293    fn get_current_time() -> Duration {
294        let mut ts = timespec::default();
295
296        unsafe { clock_gettime(CLOCK_MONOTONIC, &mut ts) };
297
298        Duration::from_micros((ts.tv_sec * 1000 * 1000 + ts.tv_nsec / 1000) as u64)
299    }
300
301    /// Deprecated alias for [`System::get_current_time`]; kept for source
302    /// compatibility with code written before the rename.
303    #[inline]
304    fn get_current_time_ms() -> Duration {
305        Self::get_current_time()
306    }
307
308    /// Converts a [`Duration`] to POSIX ticks (milliseconds); see
309    /// `crate::posix::duration` for the same conversion via [`ToTick`].
310    ///
311    /// # Examples
312    ///
313    /// ```
314    /// use osal_rs::os::*;
315    /// use core::time::Duration;
316    ///
317    /// assert_eq!(System::get_from_tick(&Duration::from_millis(250)), 250);
318    /// ```
319    fn get_from_tick(duration: &Duration) -> TickType {
320        duration.as_millis().min(TickType::MAX as u128) as TickType
321    }
322
323    /// Deprecated alias for [`System::get_from_tick`]; kept for source
324    /// compatibility with code written before the rename.
325    #[inline]
326    fn get_ms_from_tick(duration: &Duration) -> TickType {
327        Self::get_from_tick(duration)
328    }
329
330    /// Number of threads known to the system: every thread spawned through
331    /// this crate's [`crate::os::Thread`] API, plus the calling thread
332    /// itself.
333    ///
334    /// # Examples
335    ///
336    /// ```
337    /// use osal_rs::os::*;
338    ///
339    /// // Just the calling thread: nothing else has been spawned yet.
340    /// assert_eq!(System::count_threads(), 1);
341    /// ```
342    fn count_threads() -> usize {
343        // +1 for the calling thread itself, which `get_all_thread()` below
344        // always reports even when it wasn't spawned through this crate's API.
345        1 + registered_thread_count()
346    }
347
348    /// Returns a [`SystemState`] snapshot of every thread known to the
349    /// system, mirroring [`System::count_threads`]'s "+1 for the caller"
350    /// accounting.
351    ///
352    /// # Examples
353    ///
354    /// ```
355    /// use osal_rs::os::*;
356    ///
357    /// let state = System::get_all_thread();
358    /// assert_eq!(state.len(), System::count_threads());
359    /// ```
360    fn get_all_thread() -> SystemState {
361        let mut tasks = all_registered_threads();
362
363        // Mirror `count_threads()`'s +1: report the calling thread even when
364        // it wasn't spawned through this crate's API. Skip it if the caller
365        // is itself a registered thread (e.g. a spawned worker calling this
366        // from within its own thread function), to avoid double-counting.
367        let caller = unsafe { pthread_self() };
368        if !tasks.iter().any(|metadata| metadata.thread == caller) {
369            tasks.push(Thread::get_metadata_from_handle(caller));
370        }
371
372        SystemState {
373            tasks,
374            total_run_time: Self::get_tick_count().min(TickType::MAX) as u32,
375        }
376    }
377
378    /// Blocks the calling thread for `ticks` (milliseconds on this
379    /// backend), automatically resuming the sleep if interrupted by a
380    /// signal before it elapsed.
381    ///
382    /// # Examples
383    ///
384    /// ```
385    /// use osal_rs::os::*;
386    ///
387    /// let before = System::get_tick_count();
388    /// System::delay(20);
389    /// assert!(System::get_tick_count() - before >= 20);
390    /// ```
391    fn delay(ticks: TickType) {
392        let mut req = timespec {
393            tv_sec: (ticks / 1000) as c_long,
394            tv_nsec: ((ticks % 1000) as c_long) * 1_000_000,
395        };
396
397        loop {
398            let mut rem = timespec::default();
399
400            if unsafe { nanosleep(&req, &mut rem) } == 0 {
401                break;
402            }
403
404            // Interrupted by a signal before `req` elapsed: `rem` holds the
405            // time still left to sleep, so resume with that. If the kernel
406            // left `rem` untouched (a real error, not EINTR), it'll be zero
407            // and the loop exits instead of spinning forever.
408            if rem.tv_sec == 0 && rem.tv_nsec == 0 {
409                break;
410            }
411
412            req = rem;
413        }
414    }
415
416    /// Blocks until `*previous_wake_time + time_increment` (absolute ticks),
417    /// then advances `*previous_wake_time` by `time_increment` - a fixed
418    /// period loop that doesn't drift with the time spent doing work each
419    /// iteration, unlike calling [`System::delay`] with the same increment
420    /// every time.
421    ///
422    /// # Examples
423    ///
424    /// ```
425    /// use osal_rs::os::*;
426    ///
427    /// let before = System::get_tick_count();
428    /// let mut previous = before;
429    /// System::delay_until(&mut previous, 20);
430    ///
431    /// assert_eq!(previous, before + 20);
432    /// assert!(System::get_tick_count() >= previous);
433    /// ```
434    fn delay_until(previous_wake_time: &mut TickType, time_increment: TickType) {
435        let next_wake_time = previous_wake_time.saturating_add(time_increment);
436        let now = Self::get_tick_count();
437
438        if next_wake_time > now {
439            Self::delay(next_wake_time - now);
440        }
441
442        *previous_wake_time = next_wake_time;
443    }
444
445    /// Returns [`OsalRsBool::True`] once at least `time` has elapsed since
446    /// `timestamp` (both measured against [`System::get_current_time`]'s
447    /// clock).
448    ///
449    /// # Examples
450    ///
451    /// ```
452    /// use osal_rs::os::*;
453    /// use osal_rs::utils::OsalRsBool;
454    /// use core::time::Duration;
455    ///
456    /// let start = System::get_current_time();
457    /// assert_eq!(System::check_timer(&start, &Duration::from_millis(500)), OsalRsBool::False);
458    ///
459    /// System::delay(20);
460    /// assert_eq!(System::check_timer(&start, &Duration::from_millis(10)), OsalRsBool::True);
461    /// ```
462    fn check_timer(timestamp: &Duration, time: &Duration) -> OsalRsBool {
463        let elapsed = Self::get_current_time().checked_sub(*timestamp).unwrap_or_default();
464
465        if elapsed >= *time {
466            OsalRsBool::True
467        } else {
468            OsalRsBool::False
469        }
470    }
471
472    /// Yields the processor (`sched_yield(2)`) if `higher_priority_task_woken`
473    /// is non-zero, a no-op otherwise. On FreeRTOS this triggers a context
474    /// switch to a just-woken higher-priority task from within an ISR; POSIX
475    /// has no real interrupt context, so this exists purely for API
476    /// compatibility.
477    ///
478    /// # Examples
479    ///
480    /// ```
481    /// use osal_rs::os::*;
482    ///
483    /// System::yield_from_isr(1); // yields
484    /// System::yield_from_isr(0); // no-op
485    /// ```
486    fn yield_from_isr(higher_priority_task_woken: BaseType) {
487        if higher_priority_task_woken != 0 {
488            unsafe {
489                sched_yield();
490            }
491        }
492    }
493
494    /// Identical to [`System::yield_from_isr`] under a different name,
495    /// matching FreeRTOS's `portEND_SWITCHING_ISR` naming convention.
496    ///
497    /// # Examples
498    ///
499    /// ```
500    /// use osal_rs::os::*;
501    ///
502    /// System::end_switching_isr(1);
503    /// ```
504    fn end_switching_isr(switch_required: BaseType) {
505        if switch_required != 0 {
506            unsafe {
507                sched_yield();
508            }
509        }
510    }
511
512    /// No-op on POSIX: there is no real interrupt/scheduler state to guard,
513    /// unlike FreeRTOS where this disables interrupts/the scheduler.
514    ///
515    /// # Examples
516    ///
517    /// ```
518    /// use osal_rs::os::*;
519    ///
520    /// System::critical_section_enter();
521    /// System::critical_section_exit();
522    /// ```
523    fn critical_section_enter() {}
524
525    /// See [`System::critical_section_enter`].
526    fn critical_section_exit() {}
527
528    /// ISR-context counterpart of [`System::critical_section_enter`]; always
529    /// returns `0` (nothing to restore) since it's a no-op on POSIX.
530    ///
531    /// # Examples
532    ///
533    /// ```
534    /// use osal_rs::os::*;
535    ///
536    /// let saved = System::critical_section_enter_from_isr();
537    /// System::critical_section_exit_from_isr(saved);
538    /// ```
539    fn critical_section_enter_from_isr() -> UBaseType {
540        0
541    }
542
543    /// See [`System::critical_section_enter_from_isr`].
544    fn critical_section_exit_from_isr(_: UBaseType) {}
545
546    /// POSIX processes don't have a fixed heap the way FreeRTOS does (the
547    /// allocator can keep extending it via `mmap`/`brk`), so this reports
548    /// available physical memory as the closest analogue.
549    ///
550    /// # Examples
551    ///
552    /// ```
553    /// use osal_rs::os::*;
554    ///
555    /// assert!(System::get_free_heap_size() > 0);
556    /// ```
557    fn get_free_heap_size() -> usize {
558        // POSIX processes don't have a fixed heap the way FreeRTOS does (the
559        // allocator can keep extending it via mmap/brk), so this reports
560        // available physical memory as the closest analogue.
561        let page_size = unsafe { sysconf(_SC_PAGESIZE) };
562        let avail_pages = unsafe { sysconf(_SC_AVPHYS_PAGES) };
563
564        if page_size <= 0 || avail_pages <= 0 {
565            0
566        } else {
567            (page_size as usize).saturating_mul(avail_pages as usize)
568        }
569    }
570}