ove/
lib.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//! oveRTOS Rust SDK — safe wrappers for the oveRTOS embedded RTOS framework.
8//!
9//! Provides RAII types for threads, mutexes, semaphores, queues, timers, etc.
10//! and the `app!` macro that generates all FFI boilerplate so application code
11//! can be written in pure safe Rust.
12
13#![cfg_attr(not(feature = "std"), no_std)]
14
15// Panic handler (only when no_std + feature enabled)
16#[cfg(all(not(feature = "std"), feature = "panic-handler"))]
17mod panic;
18
19#[cfg(not(docsrs))]
20pub(crate) mod bindings;
21#[cfg(docsrs)]
22#[path = "bindings_stub.rs"]
23pub(crate) mod bindings;
24#[cfg(has_audio)]
25pub mod audio;
26#[cfg(has_board)]
27pub mod board;
28#[cfg(has_bsp)]
29pub mod bsp;
30#[cfg(has_gpio)]
31pub mod gpio;
32#[cfg(has_led)]
33pub mod led;
34#[cfg(has_console)]
35pub mod console;
36pub mod error;
37#[cfg(has_eventgroup)]
38pub mod eventgroup;
39pub mod fmt;
40#[cfg(has_fs)]
41pub mod fs;
42#[cfg(has_lvgl)]
43pub mod lvgl;
44#[cfg(has_nvs)]
45pub mod nvs;
46pub mod priority;
47#[cfg(has_queue)]
48pub mod queue;
49#[cfg(has_shell)]
50pub mod shell;
51pub mod static_cell;
52#[cfg(has_sync)]
53pub mod sync;
54pub mod thread;
55#[cfg(has_time)]
56pub mod time;
57#[cfg(has_timer)]
58pub mod timer;
59#[cfg(has_stream)]
60pub mod stream;
61#[cfg(has_watchdog)]
62pub mod watchdog;
63#[cfg(has_workqueue)]
64pub mod workqueue;
65#[cfg(has_infer)]
66pub mod infer;
67#[cfg(has_net)]
68pub mod net;
69#[cfg(has_net_http)]
70pub mod net_http;
71#[cfg(has_net_mqtt)]
72pub mod net_mqtt;
73#[cfg(has_net_httpd)]
74pub mod net_httpd;
75#[cfg(has_net_tls)]
76pub mod net_tls;
77#[cfg(has_net_sntp)]
78pub mod net_sntp;
79#[cfg(has_uart)]
80pub mod uart;
81#[cfg(has_spi)]
82pub mod spi;
83#[cfg(has_i2c)]
84pub mod i2c;
85#[cfg(has_pm)]
86pub mod pm;
87
88/// Raw FFI bindings re-exported for advanced use cases.
89///
90/// Direct use of these types bypasses all safety checks. Prefer the safe
91/// wrappers in sibling modules whenever possible.
92pub mod ffi {
93    pub use crate::bindings::*;
94}
95
96// Public re-exports for convenience
97pub use error::{Error, Result, WAIT_FOREVER};
98#[cfg(has_eventgroup)]
99pub use eventgroup::{EventGroup, WaitFlags, EG_CLEAR_ON_EXIT, EG_WAIT_ALL};
100pub use priority::Priority;
101#[cfg(has_queue)]
102pub use queue::Queue;
103#[cfg(has_sync)]
104pub use sync::{CondVar, Event, Mutex, MutexGuard, RecursiveMutex, RecursiveMutexGuard, Semaphore};
105pub use thread::{Thread, ThreadState, ThreadStats, MemStats, ThreadInfo};
106pub use static_cell::{StaticCell, StaticMut};
107#[cfg(has_timer)]
108pub use timer::Timer;
109pub use fmt::FmtBuf;
110#[cfg(has_stream)]
111pub use stream::Stream;
112#[cfg(has_watchdog)]
113pub use watchdog::Watchdog;
114#[cfg(has_workqueue)]
115pub use workqueue::{Work, Workqueue};
116
117/// Write a message to the oveRTOS console.
118pub fn log(msg: &[u8]) {
119    unsafe {
120        bindings::ove_console_write(msg.as_ptr() as *const _, msg.len() as u32);
121    }
122}
123
124/// Format arguments into a stack buffer and log via [`log()`].
125///
126/// Uses a 128-byte stack buffer.  Output is silently truncated if it exceeds
127/// the buffer capacity.
128///
129/// # Example
130///
131/// ```ignore
132/// ove::log_fmt!("[I] count = {}\n", 42);
133/// ```
134#[macro_export]
135macro_rules! log_fmt {
136    ($($arg:tt)*) => {{
137        use core::fmt::Write;
138        let mut buf = [0u8; 128];
139        let mut w = $crate::FmtBuf::new(&mut buf);
140        let _ = write!(w, $($arg)*);
141        $crate::log(w.as_bytes());
142    }};
143}
144
145/// Internal helper: format a prefixed, newline-terminated log message and
146/// write it to the console.  Uses a 256-byte stack buffer matching the C
147/// `_OVE_LOG_OUTPUT` macro.
148#[doc(hidden)]
149#[macro_export]
150macro_rules! _log_prefixed {
151    ($prefix:expr, $($arg:tt)*) => {{
152        use core::fmt::Write;
153        let mut buf = [0u8; 256];
154        let mut w = $crate::FmtBuf::new(&mut buf);
155        let _ = write!(w, $prefix);
156        let _ = write!(w, $($arg)*);
157        let _ = write!(w, "\n");
158        $crate::log(w.as_bytes());
159    }};
160}
161
162/// Log an informational message with `[I]` prefix and automatic newline.
163///
164/// Produces the same console output as the C `OVE_LOG_INF` macro.
165///
166/// # Example
167///
168/// ```ignore
169/// ove::log_inf!("Consumer: count = {}", val);
170/// // Output: [I] Consumer: count = 42\n
171/// ```
172#[macro_export]
173macro_rules! log_inf {
174    ($($arg:tt)*) => { $crate::_log_prefixed!("[I] ", $($arg)*) };
175}
176
177/// Log a warning message with `[W]` prefix and automatic newline.
178///
179/// Produces the same console output as the C `OVE_LOG_WRN` macro.
180#[macro_export]
181macro_rules! log_wrn {
182    ($($arg:tt)*) => { $crate::_log_prefixed!("[W] ", $($arg)*) };
183}
184
185/// Log an error message with `[E]` prefix and automatic newline.
186///
187/// Produces the same console output as the C `OVE_LOG_ERR` macro.
188#[macro_export]
189macro_rules! log_err {
190    ($($arg:tt)*) => { $crate::_log_prefixed!("[E] ", $($arg)*) };
191}
192
193/// Declare a safe accessor for TFLite model data generated by `convert.py`.
194///
195/// Generates the `extern "C"` linkage for the `_model_data` and
196/// `_model_data_len` symbols and wraps them in a function returning
197/// `&'static [u8]`.  No `extern` or `unsafe` needed in application code.
198///
199/// # Example
200///
201/// ```ignore
202/// ove::model_data!(preprocessor_model,
203///     g_audio_preprocessor_int8_model_data,
204///     g_audio_preprocessor_int8_model_data_len);
205///
206/// let data: &[u8] = preprocessor_model();
207/// ```
208#[macro_export]
209macro_rules! model_data {
210    ($fn_name:ident, $data_sym:ident, $len_sym:ident) => {
211        unsafe extern "C" {
212            safe static $data_sym: u8;
213            safe static $len_sym: u32;
214        }
215        fn $fn_name() -> &'static [u8] {
216            unsafe {
217                core::slice::from_raw_parts(
218                    &$data_sym,
219                    $len_sym as usize,
220                )
221            }
222        }
223    };
224}
225
226/// Generate the `ove_main` entry point from a Rust closure or function.
227///
228/// # Example
229///
230/// ```ignore
231/// ove::main!(app_main);
232///
233/// fn app_main() {
234///     // create resources...
235///     ove::run();
236/// }
237/// ```
238#[macro_export]
239macro_rules! main {
240    ($entry:expr) => {
241        #[unsafe(no_mangle)]
242        pub extern "C" fn ove_main() {
243            $entry();
244        }
245    };
246}
247
248// ---------------------------------------------------------------------------
249// Unified creation macros
250//
251// These macros work in both heap and zero-heap modes. In heap mode they call
252// `new()`. In zero-heap mode they declare function-local `static` storage
253// and call `from_static()`.
254// ---------------------------------------------------------------------------
255
256/// Create a [`Mutex`] that works in both heap and zero-heap modes.
257#[cfg(has_sync)]
258#[macro_export]
259macro_rules! mutex {
260    () => {{
261        #[cfg(not(zero_heap))]
262        { $crate::Mutex::new().unwrap() }
263        #[cfg(zero_heap)]
264        {
265            static mut _S: $crate::ffi::ove_mutex_storage_t =
266                unsafe { core::mem::zeroed() };
267            unsafe { $crate::Mutex::from_static(core::ptr::addr_of_mut!(_S)) }.unwrap()
268        }
269    }};
270}
271
272/// Create a [`RecursiveMutex`] that works in both heap and zero-heap modes.
273#[cfg(has_sync)]
274#[macro_export]
275macro_rules! recursive_mutex {
276    () => {{
277        #[cfg(not(zero_heap))]
278        { $crate::RecursiveMutex::new().unwrap() }
279        #[cfg(zero_heap)]
280        {
281            static mut _S: $crate::ffi::ove_mutex_storage_t =
282                unsafe { core::mem::zeroed() };
283            unsafe { $crate::RecursiveMutex::from_static(core::ptr::addr_of_mut!(_S)) }.unwrap()
284        }
285    }};
286}
287
288/// Create a [`Semaphore`] that works in both heap and zero-heap modes.
289#[cfg(has_sync)]
290#[macro_export]
291macro_rules! semaphore {
292    ($initial:expr, $max:expr) => {{
293        #[cfg(not(zero_heap))]
294        { $crate::Semaphore::new($initial, $max).unwrap() }
295        #[cfg(zero_heap)]
296        {
297            static mut _S: $crate::ffi::ove_sem_storage_t =
298                unsafe { core::mem::zeroed() };
299            unsafe { $crate::Semaphore::from_static(
300                core::ptr::addr_of_mut!(_S), $initial, $max
301            ) }.unwrap()
302        }
303    }};
304}
305
306/// Create an [`Event`] that works in both heap and zero-heap modes.
307#[cfg(has_sync)]
308#[macro_export]
309macro_rules! event {
310    () => {{
311        #[cfg(not(zero_heap))]
312        { $crate::Event::new().unwrap() }
313        #[cfg(zero_heap)]
314        {
315            static mut _S: $crate::ffi::ove_event_storage_t =
316                unsafe { core::mem::zeroed() };
317            unsafe { $crate::Event::from_static(core::ptr::addr_of_mut!(_S)) }.unwrap()
318        }
319    }};
320}
321
322/// Create a [`CondVar`] that works in both heap and zero-heap modes.
323#[cfg(has_sync)]
324#[macro_export]
325macro_rules! condvar {
326    () => {{
327        #[cfg(not(zero_heap))]
328        { $crate::CondVar::new().unwrap() }
329        #[cfg(zero_heap)]
330        {
331            static mut _S: $crate::ffi::ove_condvar_storage_t =
332                unsafe { core::mem::zeroed() };
333            unsafe { $crate::CondVar::from_static(core::ptr::addr_of_mut!(_S)) }.unwrap()
334        }
335    }};
336}
337
338/// Create an [`EventGroup`] that works in both heap and zero-heap modes.
339#[cfg(has_eventgroup)]
340#[macro_export]
341macro_rules! eventgroup {
342    () => {{
343        #[cfg(not(zero_heap))]
344        { $crate::EventGroup::new().unwrap() }
345        #[cfg(zero_heap)]
346        {
347            static mut _S: $crate::ffi::ove_eventgroup_storage_t =
348                unsafe { core::mem::zeroed() };
349            unsafe { $crate::EventGroup::from_static(core::ptr::addr_of_mut!(_S)) }.unwrap()
350        }
351    }};
352}
353
354/// Create a [`Queue`] that works in both heap and zero-heap modes.
355///
356/// # Example
357/// ```ignore
358/// let q = ove::queue!(u32, 8);
359/// ```
360#[cfg(has_queue)]
361#[macro_export]
362macro_rules! queue {
363    ($T:ty, $N:expr) => {{
364        #[cfg(not(zero_heap))]
365        { $crate::Queue::<$T, $N>::new().unwrap() }
366        #[cfg(zero_heap)]
367        {
368            static mut _S: $crate::ffi::ove_queue_storage_t =
369                unsafe { core::mem::zeroed() };
370            static mut _B: [core::mem::MaybeUninit<$T>; $N] =
371                unsafe { core::mem::MaybeUninit::uninit().assume_init() };
372            unsafe {
373                $crate::Queue::<$T, $N>::from_static(
374                    core::ptr::addr_of_mut!(_S),
375                    core::ptr::addr_of_mut!(_B) as *mut _,
376                )
377            }.unwrap()
378        }
379    }};
380}
381
382/// Create a [`Timer`] that works in both heap and zero-heap modes.
383///
384/// # Example
385/// ```ignore
386/// let t = ove::timer!(my_callback, 100, false);
387/// ```
388#[cfg(has_timer)]
389#[macro_export]
390macro_rules! timer {
391    ($callback:expr, $period_ms:expr, $one_shot:expr) => {{
392        #[cfg(not(zero_heap))]
393        { $crate::Timer::new($callback, $period_ms, $one_shot).unwrap() }
394        #[cfg(zero_heap)]
395        {
396            static mut _S: $crate::ffi::ove_timer_storage_t =
397                unsafe { core::mem::zeroed() };
398            unsafe { $crate::Timer::from_static(
399                core::ptr::addr_of_mut!(_S), $callback, $period_ms, $one_shot
400            ) }.unwrap()
401        }
402    }};
403}
404
405/// Create a [`Thread`] that works in both heap and zero-heap modes.
406///
407/// Uses the safe `fn()` entry pattern (trampoline). The name is
408/// automatically null-terminated.
409///
410/// # Example
411/// ```ignore
412/// let t = ove::thread!("worker", my_entry, Priority::Normal, 4096);
413/// ```
414#[macro_export]
415macro_rules! thread {
416    ($name:expr, $entry:expr, $prio:expr, $stack:expr) => {{
417        #[cfg(not(zero_heap))]
418        {
419            $crate::Thread::spawn(
420                concat!($name, "\0").as_bytes(),
421                $entry, $prio, $stack
422            ).unwrap()
423        }
424        #[cfg(all(zero_heap, not(rtos_zephyr)))]
425        {
426            static mut _S: $crate::ffi::ove_thread_storage_t =
427                unsafe { core::mem::zeroed() };
428            // Align to 8 bytes (ARM AAPCS stack alignment requirement).
429            #[repr(C, align(8))]
430            struct AlignedStack([u8; $stack]);
431            static mut _STACK: AlignedStack = AlignedStack([0u8; $stack]);
432            unsafe {
433                $crate::Thread::spawn_static(
434                    core::ptr::addr_of_mut!(_S),
435                    core::ptr::addr_of_mut!(_STACK) as *mut _,
436                    concat!($name, "\0").as_bytes(),
437                    $entry, $prio, $stack
438                )
439            }.unwrap()
440        }
441        #[cfg(all(zero_heap, rtos_zephyr))]
442        {
443            static mut _S: $crate::ffi::ove_thread_storage_t =
444                unsafe { core::mem::zeroed() };
445            // Zephyr with MPU needs power-of-2 aligned stacks.
446            // Add MPU guard region (128 bytes for FPU), round total
447            // to next power of 2. align(8192) covers stacks up to
448            // ~4000 usable bytes (the common embedded case).
449            const _STACK_TOTAL: usize = ($stack + 128usize).next_power_of_two();
450            #[repr(C, align(8192))]
451            struct ZStack([u8; _STACK_TOTAL]);
452            static mut _STACK: ZStack = ZStack([0u8; _STACK_TOTAL]);
453            unsafe {
454                $crate::Thread::spawn_static(
455                    core::ptr::addr_of_mut!(_S),
456                    core::ptr::addr_of_mut!(_STACK) as *mut _,
457                    concat!($name, "\0").as_bytes(),
458                    $entry, $prio, $stack
459                )
460            }.unwrap()
461        }
462    }};
463}
464
465/// Create a [`Stream`] that works in both heap and zero-heap modes.
466///
467/// # Example
468/// ```ignore
469/// let s = ove::stream!(256, 1);
470/// ```
471#[cfg(has_stream)]
472#[macro_export]
473macro_rules! stream {
474    ($N:expr, $trigger:expr) => {{
475        #[cfg(not(zero_heap))]
476        { $crate::Stream::<$N>::new($trigger).unwrap() }
477        #[cfg(zero_heap)]
478        {
479            static mut _S: $crate::ffi::ove_stream_storage_t =
480                unsafe { core::mem::zeroed() };
481            static mut _B: [u8; $N] = [0u8; $N];
482            unsafe {
483                $crate::Stream::<$N>::from_static(
484                    core::ptr::addr_of_mut!(_S),
485                    core::ptr::addr_of_mut!(_B) as *mut _,
486                    $trigger,
487                )
488            }.unwrap()
489        }
490    }};
491}
492
493/// Create a [`Workqueue`] that works in both heap and zero-heap modes.
494///
495/// # Example
496/// ```ignore
497/// let wq = ove::workqueue!("myq", Priority::Normal, 4096);
498/// ```
499#[cfg(has_workqueue)]
500#[macro_export]
501macro_rules! workqueue {
502    ($name:expr, $prio:expr, $stack:expr) => {{
503        #[cfg(not(zero_heap))]
504        {
505            $crate::Workqueue::new(
506                concat!($name, "\0").as_bytes(), $prio, $stack
507            ).unwrap()
508        }
509        #[cfg(zero_heap)]
510        {
511            static mut _S: $crate::ffi::ove_workqueue_storage_t =
512                unsafe { core::mem::zeroed() };
513            static mut _STACK: [u8; $stack] = [0u8; $stack];
514            unsafe {
515                $crate::Workqueue::from_static(
516                    core::ptr::addr_of_mut!(_S),
517                    concat!($name, "\0").as_bytes(),
518                    $prio, $stack,
519                    core::ptr::addr_of_mut!(_STACK) as *mut _,
520                )
521            }.unwrap()
522        }
523    }};
524}
525
526/// Create a [`Work`] item that works in both heap and zero-heap modes.
527#[cfg(has_workqueue)]
528#[macro_export]
529macro_rules! work {
530    ($handler:expr) => {{
531        #[cfg(not(zero_heap))]
532        { $crate::Work::new($handler).unwrap() }
533        #[cfg(zero_heap)]
534        {
535            static mut _S: $crate::ffi::ove_work_storage_t =
536                unsafe { core::mem::zeroed() };
537            unsafe { $crate::Work::from_static(
538                core::ptr::addr_of_mut!(_S), $handler
539            ) }.unwrap()
540        }
541    }};
542}
543
544/// Create a [`Watchdog`] that works in both heap and zero-heap modes.
545#[cfg(has_watchdog)]
546#[macro_export]
547macro_rules! watchdog {
548    ($timeout_ms:expr) => {{
549        #[cfg(not(zero_heap))]
550        { $crate::Watchdog::new($timeout_ms).unwrap() }
551        #[cfg(zero_heap)]
552        {
553            static mut _S: $crate::ffi::ove_watchdog_storage_t =
554                unsafe { core::mem::zeroed() };
555            unsafe { $crate::Watchdog::from_static(
556                core::ptr::addr_of_mut!(_S), $timeout_ms
557            ) }.unwrap()
558        }
559    }};
560}
561
562/// Declare a `static` wrapped in [`StaticCell`] for cross-thread shared state.
563///
564/// # Example
565/// ```ignore
566/// ove::shared!(QUEUE: Queue<u32, 8>);
567/// // then use QUEUE.send(...) directly — Deref eliminates .get()
568/// ```
569#[macro_export]
570macro_rules! shared {
571    ($vis:vis $name:ident : $ty:ty) => {
572        $vis static $name: $crate::StaticCell<$ty> = $crate::StaticCell::new();
573    };
574}
575
576/// Declare a `static` wrapped in [`StaticMut`] for single-owner mutable state.
577///
578/// # Example
579/// ```ignore
580/// ove::shared_mut!(ENGINE: DspEngine);
581/// ```
582#[macro_export]
583macro_rules! shared_mut {
584    ($vis:vis $name:ident : $ty:ty) => {
585        $vis static $name: $crate::StaticMut<$ty> = $crate::StaticMut::new();
586    };
587}
588
589/// Start audio (if enabled) and the RTOS scheduler. Blocks forever.
590///
591/// This function must be called at the end of `ove_main` (or the function
592/// passed to the [`main!`] macro). It transfers control to the RTOS and
593/// never returns.
594pub fn run() {
595    unsafe { bindings::ove_run(); }
596}