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}