kernel/utilities/
single_thread_value.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2025.
4
5//! A container for objects accessible to a single thread.
6
7use core::cell::UnsafeCell;
8use core::mem::MaybeUninit;
9use core::sync::atomic::AtomicUsize;
10use core::sync::atomic::Ordering;
11
12use crate::platform::chip::ThreadIdProvider;
13
14/// Stages of binding a [`SingleThreadValue`] to a given thread.
15///
16/// The [`SingleThreadValue`] starts out not being bound to, and thus not being
17/// usable by any thread. Only after its construction will it be bound to a
18/// particular thread, using either the [`SingleThreadValue::bind_to_thread`],
19/// or the [`SingleThreadValue::bind_to_thread_unsafe`] methods.
20///
21/// For other these and other methods to know whether a [`SingleThreadValue`]
22/// has been bound to a thread already, and thus whether its `thread_id_and_fn`
23/// field is initialized and holds a stable, it contains an `AtomicUsize` type
24/// to indicate its current "binding stage". Over its lifetime, this stage value
25/// is strictly increasing, transitioning through its variants as per their
26/// documentation.
27#[repr(usize)]
28enum BoundToThreadStage {
29    /// This state means that the [`SingleThreadValue`] has not been bound to a
30    /// particular thread yet, and its `thread_id_and_fn` field is not
31    /// initialized.
32    ///
33    /// For the `bind_to_thread` and `bind_to_thread_unsafe` methods, this value
34    /// further means that the value can be bound to the currently running
35    /// thread.
36    Unbound = 0,
37
38    /// To bind a [`SingleThreadValue`] to the currently running thread, the
39    /// `bind_to_thread` function atomically transitions it from the `Unbound`
40    /// stage to the `Binding` stage, through a compare-exchange operation.
41    ///
42    /// When this stage is active, the `thread_id_and_fn` are not initialized,
43    /// similar to `Unbound`. However, it guards other, concurrent calls to
44    /// `bind_to_thread` from attempting to bind the value to another thread
45    /// concurrently.
46    ///
47    /// Targets without support for compare-exchange atomic operations on
48    /// `usize` types can utilize `bind_to_thread_unsafe` instead: this method
49    /// requires callers to guarantee that there are no concurrent calls to
50    /// either `bind_to_thread` OR `bind_to_thread_unsafe`. Thus, it can skip
51    /// this intermediate state: it can directly transition from `Unbound` into
52    /// `Bound`.
53    #[allow(dead_code)]
54    Binding = 1,
55
56    /// This value indicates that the [`SingleThreadValue`] is bound to a
57    /// particular thread. The `thread_id_and_fn` value is initialized and
58    /// sealed: it must not be modified any longer beyond this point, and no
59    /// mutable references may exist to it. The `Bound` state cannot be
60    /// transitioned out of.
61    Bound = 2,
62}
63
64/// A container for objects accessible to a single thread.
65///
66/// This type wraps a value of type `T: ?Sync`, accessible to only a single
67/// thread. Only that thread can obtain references to the contained value, and
68/// that thread may obtain multiple _shared_ (`&`) references to the value
69/// concurrently.
70///
71/// This container is [`Sync`], regardless of whether `T` is `Sync`. This is
72/// similar to the standard library's `LocalKey`, and thus appropriate for
73/// static allocations of values that are not themselves [`Sync`]. However,
74/// unlike `LocalKey`, it only holds a value for a single thread, determined at
75/// runtime based on the first call to [`SingleThreadValue::bind_to_thread`].
76///
77/// # Example
78///
79/// ```
80/// use core::cell::Cell;
81/// use kernel::utilities::single_thread_value::SingleThreadValue;
82///
83/// // Binding to a thread requires a "ThreadIdProvider", used to query the
84/// // thread ID of the currently running thread at runtime:
85/// enum DummyThreadIdProvider {}
86/// unsafe impl kernel::platform::chip::ThreadIdProvider for DummyThreadIdProvider {
87///     fn running_thread_id() -> usize {
88///         // Return the current thread id. We return a constant for
89///         // demonstration purposes, but doing so in practice is unsound:
90///         42
91///     }
92/// }
93///
94/// static FOO: SingleThreadValue<Cell<usize>> = SingleThreadValue::new(Cell::new(123));
95///
96/// fn main() {
97///     // Bind the value contained in the `SingleThreadValue` to the currently
98///     // running thread:
99///     FOO.bind_to_thread::<DummyThreadIdProvider>();
100///
101///     // Attempt to access the value. Returns `Some(&T)` if running from the
102///     // thread that the `SingleThreadValue` is bound to:
103///     let foo_ref = FOO.get().unwrap();
104///     foo_ref.set(foo_ref.get() + 1);
105/// }
106/// ```
107///
108/// After creating the [`SingleThreadValue`] and before trying to access the
109/// wrapped value, the [`SingleThreadValue`] must have its
110/// [`bind_to_thread`](SingleThreadValue::bind_to_thread) method called. Failing
111/// to bind it to a thread will prevent any access to the wrapped value.
112///
113/// # Single-thread Synchronization
114///
115/// It is possible for the same thread to get multiple, shared, references. As a
116/// result, users must use interior mutability (e.g.
117/// [`Cell`](core::cell::Cell), [`MapCell`](tock_cells::map_cell::MapCell), or
118/// [`TakeCell`](tock_cells::take_cell::TakeCell)) to allow obtaining exclusive
119/// mutable access.
120///
121/// # Guaranteeing Single-Thread Access
122///
123/// [`SingleThreadValue`] is safe because it guarantees that the value is only
124/// ever accessed from a single thread. To do this, [`SingleThreadValue`]
125/// inspects the currently running thread on every access to the wrapped
126/// value. If the active thread is different than the original thread then the
127/// caller will not be able to access the value.
128///
129/// This requires that the system provides a correct implementation of
130/// [`ThreadIdProvider`] to identify the currently executing thread. Internally,
131/// [`SingleThreadValue`] uses the [`ThreadIdProvider::running_thread_id`]
132/// function to identify the current thread and compares it against the thread
133/// ID that the contained value is bound to.
134//
135// # Implementation Trade-Offs
136//
137// Correctly implementing a type which can be safely shared within a single
138// thread requires balancing the following trade-offs:
139//
140// 1. Automated enforcement (either by the compiler or at runtime) vs.
141//    programmer-checked correctness.
142// 2. Runtime overhead vs. compile-time checks.
143// 3. Simplicity for users vs. easy-to-make mistakes.
144//
145// Ideally, the guarantees this type provides would be verified at compile time
146// and be simple for users to use. However, as of July 2025, we don't know how
147// to realistically meet those goals.
148//
149// This approach first chooses to have automated enforcement rather than have
150// correctness (and avoiding unsoundness) based only on carefully written code
151// and scrutiny during code reviews. This is generally consistent with the Tock
152// ethos with using Rust and APIs to enforce correctness, even if the check
153// occurs at runtime.
154//
155// To implement the automated check, this type uses runtime checks. We don't
156// know a realistic way to move those checks to compile time.
157//
158// One downside to these decisions is the need to bind the value to a thread
159// _after_ the type is created. While this is conceptually similar to providing
160// a callback client after an object is constructed, which is widely used in
161// Tock, forgetting this operation will lead to the runtime check always failing
162// and will break the desired functionality of using the wrapped value.
163// However, passing in the type later is needed because the thread identifier is
164// likely not available where users want to construct this type.
165//
166// Ultimately, these trade-offs, runtime checks and an interface where missing a
167// call leads to failures, were deemed acceptable and worth the upside
168// (guaranteed soundness). In part, this is because this type should be used and
169// accessed sparingly within Tock. The complexity of creating this type stems
170// from its general risky-ness: enabling sharing static global variables. While
171// necessary for certain use cases within Tock, this should not be used lightly.
172pub struct SingleThreadValue<T> {
173    /// The contained value, made accessible to a single thread only.
174    value: T,
175
176    /// Shared atomic state to indicate whether this type is already bound to a
177    /// particular thread, or in the process of being bound to a particular
178    /// thread. Assumes values of [`BoundToThreadStage`]. Consider that type's
179    /// documentation for how `bound_to_thread` is used.
180    bound_to_thread: AtomicUsize,
181
182    /// Context used to determine which thread ID this type is bound to, and how
183    /// to determine the currently running thread ID.
184    ///
185    /// This value must only be used in accordance with the rules around
186    /// [`BoundToThreadStage`]. Consider that type's documentation for more
187    /// information. Whether this value is initialized, and when it is safe to
188    /// read and write depend on the value of `bound_to_thread`.
189    thread_id_and_fn: UnsafeCell<MaybeUninit<(fn() -> usize, usize)>>,
190}
191
192/// Mark that [`SingleThreadValue`] is [`Sync`] to enable multiple accesses.
193///
194/// # Safety
195///
196/// This is safe because [`SingleThreadValue`] enforces that the shared value
197/// is only ever accessed from a single thread.
198unsafe impl<T> Sync for SingleThreadValue<T> {}
199
200impl<T> SingleThreadValue<T> {
201    /// Create a [`SingleThreadValue`].
202    ///
203    /// Note, the value will not be accessible immediately after `new()` runs.
204    /// It must first be bound to a particular thread, using the
205    /// [`bind_to_thread`](SingleThreadValue::bind_to_thread) or
206    /// [`bind_to_thread_unsafe`](SingleThreadValue::bind_to_thread_unsafe) methods.
207    pub const fn new(value: T) -> Self {
208        Self {
209            value,
210            bound_to_thread: AtomicUsize::new(BoundToThreadStage::Unbound as usize),
211            thread_id_and_fn: UnsafeCell::new(MaybeUninit::uninit()),
212        }
213    }
214
215    /// Bind this [`SingleThreadValue`] to the currently running thread.
216    ///
217    /// If this [`SingleThreadValue`] is not already bound to a thread, or if it
218    /// is not currently in the process of binding to a thread, then this binds
219    /// the [`SingleThreadValue`] to the current thread.
220    ///
221    /// It further records the [`ThreadIdProvider::running_thread_id`] function
222    /// reference, and uses this function to determine the currently running
223    /// thread for any future queries.
224    ///
225    /// Returns `true` if this invocation successfully bound the value to the
226    /// current thread. Otherwise, if the value was already bound to this same
227    /// or another thread, or is concurrently being bound to a thread, it
228    /// returns `false`.
229    ///
230    /// This method requires the target to support atomic operations
231    /// (namely, `compare_exchange`) on `usize`-sized values, and thus
232    /// relies on the `cfg(target_has_atomic = "ptr")` conditional.
233    #[cfg(target_has_atomic = "ptr")]
234    pub fn bind_to_thread<P: ThreadIdProvider>(&self) -> bool {
235        // For the check whether we're already bound to a thread, `Relaxed`
236        // ordering is fine: we don't actually care about the value in
237        // `thread_id_and_fn`, and don't need previous writes to it to be
238        // visible to this thread.
239        //
240        // Perform a compare-exchange on the `bound_to_thread` value. If this
241        // operation is successful, the [`SingleThreadValue`] was in the
242        // `Unbound` stage, but is now in the `Binding` stage. This "reserves"
243        // to be initialized: other concurrent accesses observing a `Binding`
244        // stage must not assume that `thread_id_and_fn` is initialized, but
245        // also will not attempt to being initialization themselves:
246        if self
247            .bound_to_thread
248            .compare_exchange(
249                // Expected current value:
250                BoundToThreadStage::Unbound as usize,
251                // New value:
252                BoundToThreadStage::Binding as usize,
253                // Success memory ordering:
254                Ordering::Relaxed,
255                // Failure memory ordering:
256                Ordering::Relaxed,
257            )
258            .is_err()
259        {
260            return false;
261        }
262
263        // Great, we have reserved the value for initialization!
264        //
265        // Write the current thread ID, and the function symbol used to query
266        // the currently running thread ID.
267        let ptr_thread_id_and_fn = self.thread_id_and_fn.get();
268
269        // # Safety
270        //
271        // We must ensure that there are no (mutable) aliases or concurrent
272        // reads or writes of the thread_id_and_fn value.
273        //
274        // This value is accessed in three functions: `bind_to_thread_unsafe`,
275        // `bind_to_thread`, and `bound_to_current_thread`:
276        //
277        // - `bind_to_thread_unsafe`: Callers of that function guarantee that
278        //   there are no concurrent invocations of it together with our
279        //   current function, `bind_to_thread`. Thus, no concurrent call to
280        //   that other function can hold an alias, or perform a read or write
281        //   of this value.
282        //
283        // - `bind_to_thread`: Before obtaining a reference to the
284        //   `thread_id_and_fn` value or reading/writing it, this function
285        //   performs a compare-exchange operation on the `bound_to_thread`
286        //   value, ensuring that it is currently `Unbound`, and atomtically
287        //   transitioning it towards `Binding`.
288        //
289        //   Our own compare-exchange operation on this value was successful. As
290        //   `Binding` cannot transition back to `Unbound`, there can only be a
291        //   single `bind_to_thread` call to successfully perform this
292        //   compare-exchange operation per[`SingleThreadValue`].
293        //
294        //   If another concurrent call had performed this compare-exchange
295        //   successfully, our own operation would have failed. Given that we
296        //   successfully performed this operation, all other concurrent calls
297        //   to this function must fail this operation instead, and thus will
298        //   not take this if-branch, and therefore will not attempt to access
299        //   the `thread_id_and_fn` value.
300        //
301        // - `bound_to_current_thread`: This function is allowed to run
302        //   concurrently with `bind_to_thread`. However, it only accesses the
303        //   `thread_id_and_fn` value when the `bound_to_thread` atomic
304        //   contains `Bound`.
305        //
306        //   The compare and swap has transitioned this value from `Unbound` to
307        //   `Binding`, and no concurrent call can further *modify*
308        //   `bound_to_thread` other than our own function instance. This
309        //   currently running function will only transition the
310        //   `bound_to_thread` value from `Binding` to `Bound` once it has
311        //   initialized `thread_id_and_fn` and all references to it cease to
312        //   exist. Thus, no concurrent calls to `bound_to_current_thread` will
313        //   read the `thread_id_and_fn` before that point.
314        //
315        // Hence this operation is sound.
316        unsafe {
317            *ptr_thread_id_and_fn =
318                MaybeUninit::new((P::running_thread_id, P::running_thread_id()));
319        }
320
321        // When initializing the `SingleThreadValue`, we must use `Release`
322        // ordering on the `bound_to_thread` store. This ensures that any
323        // subsequent atomic load of this value with at least `Acquire`
324        // ordering constraints will observe the previous write to
325        // `thread_id_and_fn`).
326        self.bound_to_thread
327            .store(BoundToThreadStage::Bound as usize, Ordering::Release);
328
329        // We have successfully bound this `SingleThreadValue` to the
330        // currently running thread:
331        true
332    }
333
334    /// Bind this [`SingleThreadValue`] to the currently running thread.
335    ///
336    /// If this [`SingleThreadValue`] is not already bound to a thread, or if it
337    /// is not currently in the process of binding to a thread, then this binds
338    /// the [`SingleThreadValue`] to the current thread.
339    ///
340    /// It further records the [`ThreadIdProvider::running_thread_id`] function
341    /// reference, and uses this function to determine the currently running
342    /// thread for any future queries.
343    ///
344    /// Returns `true` if this invocation successfully bound the value to the
345    /// current thread. Otherwise, if the value was already bound to this same
346    /// or another thread, it returns `false`.
347    ///
348    /// This method is `unsafe`, and does not require the target to support
349    /// atomic operations (namely, `compare_exchange`) on `usize`-sized values.
350    ///
351    /// # Safety
352    ///
353    /// Callers of this function must ensure that this function is never called
354    /// concurrently with other calls to
355    /// [`bind_to_thread`](SingleThreadValue::bind_to_thread) or
356    /// [`bind_to_thread_unsafe`](SingleThreadValue::bind_to_thread_unsafe) on
357    /// the same [`SingleThreadValue`] instance.
358    pub unsafe fn bind_to_thread_unsafe<P: ThreadIdProvider>(&self) -> bool {
359        // For the check whether we're already bound to a thread, `Relaxed`
360        // ordering is fine: we don't actually care about the value in
361        // `thread_id_and_fn`, and don't need previous writes to it to be
362        // visible to this thread.
363        if self.bound_to_thread.load(Ordering::Relaxed) != BoundToThreadStage::Unbound as usize {
364            // This value is already bound to a thread
365            return false;
366        }
367
368        // Write the current thread ID, and the function symbol used to
369        // query the currently running thread ID.
370        let ptr_thread_id_and_fn = self.thread_id_and_fn.get();
371
372        // # Safety
373        //
374        // We must ensure that there are no (mutable) aliases or concurrent
375        // reads or writes of the `thread_id_and_fn` value.
376        //
377        // This value is accessed in three functions: `bind_to_thread`,
378        // `bind_to_thread_unsafe`, and `bound_to_current_thread`:
379        //
380        // - `bind_to_thread` & `bind_to_thread_unsafe`: Callers of this current
381        //   function guarantee that there are no concurrent invocations of
382        //   either `bind_to_thread` or `bind_to_thread_unsafe` on the same
383        //   `SingleThreadValue` object. Thus, no concurrent call to either of
384        //   these functions can hold an alias, or perform a read or write of
385        //   the `thread_id_and_fn` value.
386        //
387        // - `bound_to_current_thread`: This function is allowed to run
388        //   concurrently with `bind_to_thread`. However, it only accesses this
389        //   value when the `bound_to_thread` atomic contains `Bound`.
390        //   `bound_to_current_thread` never writes to `bound_to_thread`.
391        //
392        //   This current function has, before taking this if-branch, checked
393        //   that `bound_to_thread` is currently `Unbound`, and will continue
394        //   to be `Unbound` for the duration of the write to
395        //   `thread_id_and_fn`, as there are no concurrent calls to
396        //   `bind_to_thread` or `bind_to_thread_unsafe`. It only transitions
397        //   `bound_to_thread` to `Bound` after completing the initialization
398        //   of `thread_id_and_fn` and all references to that value cease to
399        //   exist.
400        //
401        // Thus, this operation is sound.
402        unsafe {
403            *ptr_thread_id_and_fn =
404                MaybeUninit::new((P::running_thread_id, P::running_thread_id()));
405        }
406
407        // When initializing the `SingleThreadValue`, we must use `Release`
408        // ordering on the `bound_to_thread` store. This ensures that any
409        // subsequent atomic load of this value with at least `Acquire`
410        // ordering constraints will observe the previous write to
411        // `thread_id_and_fn`).
412        self.bound_to_thread
413            .store(BoundToThreadStage::Bound as usize, Ordering::Release);
414
415        // We have successfully bound this `SingleThreadValue` to the
416        // currently running thread:
417        true
418    }
419
420    /// Check whether this `SingleThreadValue` instance is bound to the
421    /// currently running thread ID.
422    ///
423    /// Returns `true` if the value is bound to the thread that is currently
424    /// running (as determined by the implementation of `ThreadIdProvider` used
425    /// in [`bind_to_thread`](SingleThreadValue::bind_to_thread) or
426    /// [`bind_to_thread_unsafe`](SingleThreadValue::bind_to_thread_unsafe)).
427    /// Returns `false` when a different thread is executing, when it is not
428    /// bound to any thread yet, or currently in the process of being bound to a
429    /// thread.
430    pub fn bound_to_current_thread(&self) -> bool {
431        // This function loads the `thread_id_and_fn` field, written by either
432        // `bind_to_thread` or `bind_to_thread_unsafe` before setting
433        // `bound_to_thread` to `Bound` with `Release` ordering constraints. To
434        // make those writes visible, we need to load the `bound_to_thread`
435        // value with at least `Acquire` ordering constraints:
436        if self.bound_to_thread.load(Ordering::Acquire) != BoundToThreadStage::Bound as usize {
437            // The `SingleThreadValue` value is not yet bound to any thread:
438            return false;
439        }
440
441        // The `SingleThreadValue` value is bound to _a_ thread, check whether
442        // it is the currently running thread:
443        let ptr_thread_id_and_fn: *mut MaybeUninit<(fn() -> usize, usize)> =
444            self.thread_id_and_fn.get();
445
446        // # Safety
447        //
448        // We must ensure that there are no (mutable) aliases or concurrent
449        // reads or writes of the thread_id_and_fn value.
450        //
451        // This value is accessed in three functions: `bind_to_thread`,
452        // `bind_to_thread_unsafe`, and `bound_to_current_thread`:
453        //
454        // - `bind_to_thread` / `bind_to_thread_unsafe`: Before creating a
455        //   reference to, reading from, or writing to the `bound_to_thread`
456        //   value, both functions check that `bound_to_thread` is not already
457        //   set to `Bound`. However, when reaching if-branch in this current
458        //   function, we have observed that `bound_to_thread` is set to
459        //   `Bound`. The state of `Bound` cannot be transitioned out of. Thus,
460        //   `bound_to_thread` will never be written again.
461        //
462        //   `bound_to_thread` is only ever set to `Bound`, *after*
463        //   `thread_id_and_fn` has been initialized, and all references to
464        //   `thread_id_and_fn` cease to exist.
465        //
466        //   When reaching this point, neither `bind_to_thread` nor
467        //   `bind_to_thread_unsafe` will ever read from, write to, or create a
468        //   reference to `thread_id_and_fn` again.
469        //
470        // - `bound_to_current_thread`: This function is allowed to run
471        //   concurrently with other calls to `bound_to_current_thread`.
472        //
473        //   However, this function only creates shared / immutable references
474        //   to this value that are never written. Multiple shared references
475        //   to this value are allowed to exist, so long as they are not
476        //   aliased by another exclusive / mutable reference, and the
477        //   underlying memory is not modified otherwise.
478        //
479        //   The only functions writing to `thread_id_and_fn` are
480        //   `bind_to_thread` and `bind_to_thread_unsafe`. As stated above,
481        //   when reaching this point, neither functions will ever write to
482        //   `thread_id_and_fn` again, and all of their internal references
483        //   have ceased to exist.
484        //
485        // Thus, this operation is sound.
486        let maybe_thread_id_and_fn: &MaybeUninit<(fn() -> usize, usize)> =
487            unsafe { &*(ptr_thread_id_and_fn as *const _) };
488
489        // # Safety
490        //
491        // Both `bind_to_thread` and `bind_to_thread_unsafe` are guaranteed to
492        // have initialized `thread_id_and_fn` *before* setting
493        // `bound_to_thread` to `Bound` with `Release` ordering constraints.
494        //
495        // In this if-branch, `bound_to_thread` has been observed to be `Bound`,
496        // a state that cannot be transitioned out of. We have loaded this
497        // value with `Acquire` ordering constraints, making all values written
498        // by other threads before a write to `bound_to_thread` with `Release`
499        // constraints visible to this thread.
500        //
501        // Thus, we can safely rely on `thread_id_and_fn` to be initialized:
502        let (running_thread_id_fn, bound_thread_id) =
503            unsafe { maybe_thread_id_and_fn.assume_init() };
504
505        // Finally, check if the thread this `SingleThreadValue` is bound to
506        // is the running thread ID:
507        bound_thread_id == running_thread_id_fn()
508    }
509
510    /// Obtain a reference to the contained value.
511    ///
512    /// This function checks whether the [`SingleThreadValue`] is bound to the
513    /// currently running thread and, if so, returns a shared / immutable
514    /// reference to its contained value.
515    pub fn get(&self) -> Option<&T> {
516        if self.bound_to_current_thread() {
517            Some(&self.value)
518        } else {
519            None
520        }
521    }
522}