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> {}