ove/init_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//! `InitCell<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 InitCell<T> {
29 initialized: AtomicBool,
30 inner: UnsafeCell<Option<T>>,
31}
32
33impl<T> Default for InitCell<T> {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl<T> InitCell<T> {
40 /// Create an empty cell. Usable in `static` declarations.
41 pub const fn new() -> Self {
42 Self {
43 initialized: AtomicBool::new(false),
44 inner: UnsafeCell::new(None),
45 }
46 }
47
48 /// Initialize the cell with `val`.
49 ///
50 /// # Panics
51 /// Panics if the cell is already initialized.
52 ///
53 /// # Safety contract
54 /// Must be called single-threaded (e.g. in `on_init`).
55 pub fn init(&self, val: T) {
56 assert!(
57 !self.initialized.load(Ordering::Acquire),
58 "InitCell::init called on already-initialized cell"
59 );
60 // SAFETY: Single-threaded during init (lifecycle guarantee).
61 unsafe {
62 *self.inner.get() = Some(val);
63 }
64 self.initialized.store(true, Ordering::Release);
65 }
66
67 /// Get a reference to the contained value.
68 ///
69 /// # Panics
70 /// Panics if the cell has not been initialized.
71 pub fn get(&self) -> &T {
72 // Acquire-load pairs with the init/shutdown release-stores so a
73 // reader observing `initialized=true` is guaranteed to also see
74 // the fully-written `inner = Some(val)`. Relaxed was previously
75 // used on the assumption of single-CPU lifecycle; cross-thread
76 // setup→worker access patterns (e.g. workqueue spawning a worker
77 // before WQ_WORK is initialized) require the explicit fence.
78 assert!(
79 self.initialized.load(Ordering::Acquire),
80 "InitCell::get called on uninitialized cell"
81 );
82 // SAFETY: Acquire-load above synchronises with init's Release
83 // store; the inner value is fully visible.
84 unsafe { (*self.inner.get()).as_ref().unwrap() }
85 }
86
87 /// Try to initialize the cell. Returns `Err(val)` if already initialized,
88 /// so the caller doesn't lose the value.
89 ///
90 /// # Safety contract
91 /// Same as `init()` — must be called single-threaded.
92 pub fn try_init(&self, val: T) -> Result<(), T> {
93 if self.initialized.load(Ordering::Acquire) {
94 return Err(val);
95 }
96 // SAFETY: Single-threaded during init (lifecycle guarantee).
97 unsafe {
98 *self.inner.get() = Some(val);
99 }
100 self.initialized.store(true, Ordering::Release);
101 Ok(())
102 }
103
104 /// Try to get a reference, returning `None` if not initialized.
105 #[inline]
106 pub fn try_get(&self) -> Option<&T> {
107 // Acquire-load — see `get()`. This call is on the bench-test hot
108 // path *and* on worker-thread paths (handler reads shared cells)
109 // so the acquire is correctness-required, not just paranoia.
110 if !self.initialized.load(Ordering::Acquire) {
111 return None;
112 }
113 // SAFETY: Acquire above synchronises with init's Release store.
114 unsafe { (*self.inner.get()).as_ref() }
115 }
116
117 /// Drop the contained value and mark the cell as uninitialized.
118 ///
119 /// Idempotent — no-op if already empty.
120 ///
121 /// # Safety contract
122 /// Must be called single-threaded (e.g. in `on_shutdown`).
123 pub fn shutdown(&self) {
124 if self.initialized.load(Ordering::Acquire) {
125 self.initialized.store(false, Ordering::Release);
126 // SAFETY: Single-threaded during shutdown (lifecycle guarantee).
127 // No other thread can observe the value after initialized=false.
128 unsafe {
129 *self.inner.get() = None;
130 }
131 }
132 }
133}
134
135impl<T> core::ops::Deref for InitCell<T> {
136 type Target = T;
137 fn deref(&self) -> &T {
138 self.get()
139 }
140}
141
142// SAFETY: The init/shutdown lifecycle is single-threaded. Between those points
143// the value is immutable (shared &T only). The AtomicBool + Release/Acquire
144// ordering ensures cross-thread visibility.
145unsafe impl<T: Send + Sync> Sync for InitCell<T> {}
146unsafe impl<T: Send> Send for InitCell<T> {}
147
148/// Like `InitCell` but provides `&mut T` access through `UnsafeCell`.
149///
150/// Used for types that need mutable access from a single owner thread
151/// (e.g., DspEngine from audio ISR, IrManager from loader thread).
152///
153/// # Safety contract
154///
155/// - `init()` and `shutdown()` must be called single-threaded (lifecycle guarantee).
156/// - `get_mut()` requires the caller to ensure exclusive access.
157pub struct InitMut<T> {
158 initialized: AtomicBool,
159 inner: UnsafeCell<Option<T>>,
160}
161
162impl<T> Default for InitMut<T> {
163 fn default() -> Self {
164 Self::new()
165 }
166}
167
168impl<T> InitMut<T> {
169 /// Create an empty cell. Usable in `static` declarations.
170 pub const fn new() -> Self {
171 Self {
172 initialized: AtomicBool::new(false),
173 inner: UnsafeCell::new(None),
174 }
175 }
176
177 /// Initialize the cell with `val`.
178 ///
179 /// # Panics
180 /// Panics if the cell is already initialized.
181 pub fn init(&self, val: T) {
182 assert!(
183 !self.initialized.load(Ordering::Acquire),
184 "InitMut::init called on already-initialized cell"
185 );
186 unsafe { *self.inner.get() = Some(val) };
187 self.initialized.store(true, Ordering::Release);
188 }
189
190 /// Try to initialize the cell. Returns `Err(val)` if already initialized,
191 /// so the caller doesn't lose the value.
192 ///
193 /// # Safety contract
194 /// Same as `init()` — must be called single-threaded.
195 pub fn try_init(&self, val: T) -> Result<(), T> {
196 if self.initialized.load(Ordering::Acquire) {
197 return Err(val);
198 }
199 unsafe { *self.inner.get() = Some(val) };
200 self.initialized.store(true, Ordering::Release);
201 Ok(())
202 }
203
204 /// Get a mutable reference to the contained value.
205 ///
206 /// # Safety
207 /// Caller must ensure exclusive access (single-threaded or external synchronization).
208 #[allow(clippy::mut_from_ref)]
209 pub unsafe fn get_mut(&self) -> &mut T {
210 // Acquire-load synchronises with init's Release store.
211 debug_assert!(self.initialized.load(Ordering::Acquire));
212 unsafe { (*self.inner.get()).as_mut().unwrap() }
213 }
214
215 /// Try to get an immutable reference, returning `None` if not initialized.
216 #[inline]
217 pub fn try_get(&self) -> Option<&T> {
218 if !self.initialized.load(Ordering::Acquire) {
219 return None;
220 }
221 unsafe { (*self.inner.get()).as_ref() }
222 }
223
224 /// Get an immutable reference to the contained value.
225 ///
226 /// # Panics
227 /// Panics if the cell has not been initialized.
228 pub fn get(&self) -> &T {
229 assert!(
230 self.initialized.load(Ordering::Acquire),
231 "InitMut::get called on uninitialized cell"
232 );
233 unsafe { (*self.inner.get()).as_ref().unwrap() }
234 }
235
236 /// Drop the contained value and mark the cell as uninitialized.
237 ///
238 /// Idempotent — no-op if already empty.
239 pub fn shutdown(&self) {
240 if self.initialized.load(Ordering::Acquire) {
241 self.initialized.store(false, Ordering::Release);
242 unsafe { *self.inner.get() = None };
243 }
244 }
245}
246
247impl<T> core::ops::Deref for InitMut<T> {
248 type Target = T;
249 fn deref(&self) -> &T {
250 self.get()
251 }
252}
253
254// SAFETY: InitMut provides `&mut T` access only through an unsafe method
255// that requires the caller to prove exclusive access. Init/shutdown are
256// single-threaded (lifecycle guarantee). `T: Sync` is required because
257// shared references (`&T` via `Deref`) may be accessed from multiple threads.
258unsafe impl<T: Send + Sync> Sync for InitMut<T> {}
259unsafe impl<T: Send> Send for InitMut<T> {}