ove/
static_cell.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//! Init-once container for `static` declarations.
8//!
9//! `StaticCell<T>` encapsulates the single `unsafe` pattern of "create once in
10//! `on_init`, use from any thread, destroy in `on_shutdown`" behind a safe API.
11
12use core::cell::UnsafeCell;
13use core::sync::atomic::{AtomicBool, Ordering};
14
15/// A `no_std` init-once container designed for the oveRTOS lifecycle.
16///
17/// - `const fn new()` — usable in `static` declarations
18/// - `init()` — set value (panics if already set)
19/// - `get()` — get reference (panics if not set)
20/// - `try_get()` — non-panicking variant
21/// - `shutdown()` — drop contained value, mark uninitialized
22///
23/// # Safety contract
24///
25/// `init()` and `shutdown()` must be called single-threaded (framework lifecycle
26/// guarantee: `on_init` / `on_shutdown`). Between init and shutdown the value is
27/// immutable — no data race is possible.
28pub struct StaticCell<T> {
29    initialized: AtomicBool,
30    inner: UnsafeCell<Option<T>>,
31}
32
33impl<T> StaticCell<T> {
34    /// Create an empty cell. Usable in `static` declarations.
35    pub const fn new() -> Self {
36        Self {
37            initialized: AtomicBool::new(false),
38            inner: UnsafeCell::new(None),
39        }
40    }
41
42    /// Initialize the cell with `val`.
43    ///
44    /// # Panics
45    /// Panics if the cell is already initialized.
46    ///
47    /// # Safety contract
48    /// Must be called single-threaded (e.g. in `on_init`).
49    pub fn init(&self, val: T) {
50        if self.initialized.load(Ordering::Relaxed) {
51            panic!("StaticCell::init called on already-initialized cell");
52        }
53        // SAFETY: Single-threaded during init (lifecycle guarantee).
54        unsafe {
55            *self.inner.get() = Some(val);
56        }
57        self.initialized.store(true, Ordering::Release);
58    }
59
60    /// Get a reference to the contained value.
61    ///
62    /// # Panics
63    /// Panics if the cell has not been initialized.
64    pub fn get(&self) -> &T {
65        if !self.initialized.load(Ordering::Acquire) {
66            panic!("StaticCell::get called on uninitialized cell");
67        }
68        // SAFETY: After init (Release) + this Acquire, the value is immutable
69        // until shutdown (which is single-threaded). No data race.
70        unsafe { (*self.inner.get()).as_ref().unwrap() }
71    }
72
73    /// Try to initialize the cell. Returns `Err(val)` if already initialized,
74    /// so the caller doesn't lose the value.
75    ///
76    /// # Safety contract
77    /// Same as `init()` — must be called single-threaded.
78    pub fn try_init(&self, val: T) -> Result<(), T> {
79        if self.initialized.load(Ordering::Relaxed) {
80            return Err(val);
81        }
82        // SAFETY: Single-threaded during init (lifecycle guarantee).
83        unsafe {
84            *self.inner.get() = Some(val);
85        }
86        self.initialized.store(true, Ordering::Release);
87        Ok(())
88    }
89
90    /// Try to get a reference, returning `None` if not initialized.
91    pub fn try_get(&self) -> Option<&T> {
92        if !self.initialized.load(Ordering::Acquire) {
93            return None;
94        }
95        // SAFETY: Same argument as `get()`.
96        unsafe { (*self.inner.get()).as_ref() }
97    }
98
99    /// Drop the contained value and mark the cell as uninitialized.
100    ///
101    /// Idempotent — no-op if already empty.
102    ///
103    /// # Safety contract
104    /// Must be called single-threaded (e.g. in `on_shutdown`).
105    pub fn shutdown(&self) {
106        if self.initialized.load(Ordering::Relaxed) {
107            self.initialized.store(false, Ordering::Release);
108            // SAFETY: Single-threaded during shutdown (lifecycle guarantee).
109            // No other thread can observe the value after initialized=false.
110            unsafe {
111                *self.inner.get() = None;
112            }
113        }
114    }
115}
116
117impl<T> core::ops::Deref for StaticCell<T> {
118    type Target = T;
119    fn deref(&self) -> &T { self.get() }
120}
121
122// SAFETY: The init/shutdown lifecycle is single-threaded. Between those points
123// the value is immutable (shared &T only). The AtomicBool + Release/Acquire
124// ordering ensures cross-thread visibility.
125unsafe impl<T: Send + Sync> Sync for StaticCell<T> {}
126unsafe impl<T: Send> Send for StaticCell<T> {}
127
128/// Like `StaticCell` but provides `&mut T` access through `UnsafeCell`.
129///
130/// Used for types that need mutable access from a single owner thread
131/// (e.g., DspEngine from audio ISR, IrManager from loader thread).
132///
133/// # Safety contract
134///
135/// - `init()` and `shutdown()` must be called single-threaded (lifecycle guarantee).
136/// - `get_mut()` requires the caller to ensure exclusive access.
137pub struct StaticMut<T> {
138    initialized: AtomicBool,
139    inner: UnsafeCell<Option<T>>,
140}
141
142impl<T> StaticMut<T> {
143    /// Create an empty cell. Usable in `static` declarations.
144    pub const fn new() -> Self {
145        Self {
146            initialized: AtomicBool::new(false),
147            inner: UnsafeCell::new(None),
148        }
149    }
150
151    /// Initialize the cell with `val`.
152    ///
153    /// # Panics
154    /// Panics if the cell is already initialized.
155    pub fn init(&self, val: T) {
156        if self.initialized.load(Ordering::Relaxed) {
157            panic!("StaticMut::init called on already-initialized cell");
158        }
159        unsafe { *self.inner.get() = Some(val) };
160        self.initialized.store(true, Ordering::Release);
161    }
162
163    /// Try to initialize the cell. Returns `Err(val)` if already initialized,
164    /// so the caller doesn't lose the value.
165    ///
166    /// # Safety contract
167    /// Same as `init()` — must be called single-threaded.
168    pub fn try_init(&self, val: T) -> Result<(), T> {
169        if self.initialized.load(Ordering::Relaxed) {
170            return Err(val);
171        }
172        unsafe { *self.inner.get() = Some(val) };
173        self.initialized.store(true, Ordering::Release);
174        Ok(())
175    }
176
177    /// Get a mutable reference to the contained value.
178    ///
179    /// # Safety
180    /// Caller must ensure exclusive access (single-threaded or external synchronization).
181    pub unsafe fn get_mut(&self) -> &mut T {
182        debug_assert!(self.initialized.load(Ordering::Acquire));
183        unsafe { (*self.inner.get()).as_mut().unwrap() }
184    }
185
186    /// Try to get an immutable reference, returning `None` if not initialized.
187    pub fn try_get(&self) -> Option<&T> {
188        if !self.initialized.load(Ordering::Acquire) {
189            return None;
190        }
191        unsafe { (*self.inner.get()).as_ref() }
192    }
193
194    /// Get an immutable reference to the contained value.
195    ///
196    /// # Panics
197    /// Panics if the cell has not been initialized.
198    pub fn get(&self) -> &T {
199        if !self.initialized.load(Ordering::Acquire) {
200            panic!("StaticMut::get called on uninitialized cell");
201        }
202        unsafe { (*self.inner.get()).as_ref().unwrap() }
203    }
204
205    /// Drop the contained value and mark the cell as uninitialized.
206    ///
207    /// Idempotent — no-op if already empty.
208    pub fn shutdown(&self) {
209        if self.initialized.load(Ordering::Relaxed) {
210            self.initialized.store(false, Ordering::Release);
211            unsafe { *self.inner.get() = None };
212        }
213    }
214}
215
216impl<T> core::ops::Deref for StaticMut<T> {
217    type Target = T;
218    fn deref(&self) -> &T { self.get() }
219}
220
221// SAFETY: StaticMut provides `&mut T` access only through an unsafe method
222// that requires the caller to prove exclusive access. Init/shutdown are
223// single-threaded (lifecycle guarantee).
224unsafe impl<T: Send> Sync for StaticMut<T> {}
225unsafe impl<T: Send> Send for StaticMut<T> {}