Skip to main content

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