zephyr/
object.rs

1//! # Zephyr Kernel Objects
2//!
3//! Zephyr has a concept of a 'kernel object' that is handled a bit magically.  In kernel mode
4//! threads, these are just pointers to the data structures that Zephyr uses to manage that item.
5//! In userspace, they are still pointers, but those data structures aren't accessible to the
6//! thread.  When making syscalls, the kernel validates that the objects are both valid kernel
7//! objects and that the are supposed to be accessible to this thread.
8//!
9//! In many Zephyr apps, the kernel objects in the app are defined as static, using special macros.
10//! These macros make sure that the objects get registered so that they are accessible to userspace
11//! (at least after that access is granted).
12//!
13//! There are also kernel objects that are synthesized as part of the build.  Most notably, there
14//! are ones generated by the device tree.
15//!
16//! ## Safety
17//!
18//! Zephyr has traditionally not focused on safety.  Early versions of project goals, in fact,
19//! emphasized performance and small code size as priorities over runtime checking of safety.  Over
20//! the years, this focus has changed a bit, and Zephyr does contain some additional checking, some
21//! of which is optional.
22//!
23//! Zephyr is still constrained at compile time to checks that can be performed with the limits
24//! of the C language.  With Rust, we have a much greater ability to enforce many aspects of safety
25//! at compile time.  However, there is some complexity to doing this at the interface between the C
26//! world and Rust.
27//!
28//! There are two types of kernel objects we deal with.  There are kernel objects that are allocated
29//! by C code (often auto-generated) that should be accessible to Rust.  These are mostly `struct
30//! device` values, and will be handled in a devices module.  The other type are objects that
31//! application code wishes to declare statically, and use from Rust code.  That is the
32//! responsibility of this module.  (There will also be support for more dynamic management of
33//! kernel objects, but this will be handled later).
34//!
35//! Static kernel objects in Zephyr are declared as C top-level variables (where the keyword static
36//! means something different).  It is the responsibility of the calling code to initialize these
37//! items, make sure they are only initialized once, and to ensure that sharing of the object is
38//! handled properly.  All of these are concerns we can handle in Rust.
39//!
40//! To handle initialization, we pair each kernel object with a single atomic value, whose zero
41//! value indicates [`KOBJ_UNINITIALIZED`].  There are a few instances of values that can be placed
42//! into uninitialized memory in a C declaration that will need to be zero initialized as a Rust
43//! static.  The case of thread stacks is handled as a special case, where the initialization
44//! tracking is kept separate so that the stack can still be placed in initialized memory.
45//!
46//! This state goes through two more values as the item is initialized, one indicating the
47//! initialization is happening, and another indicating it has finished.
48//!
49//! For each kernel object, there will be two types.  One, having a name of the form StaticThing,
50//! and the other having the form Thing.  The StaticThing will be used in a static declaration.
51//! There is a [`kobj_define!`] macro that matches declarations of these values and adds the
52//! necessary linker declarations to place these in the correct linker sections.  This is the
53//! equivalent of the set of macros in C, such as `K_SEM_DEFINE`.
54//!
55//! This StaticThing will have a single method [`init_once`] which accepts a single argument of a
56//! type defined by the object.  For most objects, it will just be an empty tuple `()`, but it can
57//! be whatever initializer is needed for that type by Zephyr.  Semaphores, for example, take the
58//! initial value and the limit.  Threads take as an initializer the stack to be used.
59//!
60//! This `init_once` will initialize the Zephyr object and return the `Thing` item that will have
61//! the methods on it to use the object.  Attributes such as `Sync`, and `Clone` will be defined
62//! appropriately so as to match the semantics of the underlying Zephyr kernel object.  Generally
63//! this `Thing` type will simply be a container for a direct pointer, and thus using and storing
64//! these will have the same characteristics as it would from C.
65//!
66//! Rust has numerous strict rules about mutable references, namely that it is not safe to have more
67//! than one mutable reference.  The language does allow multiple `*mut ktype` references, and their
68//! safety depends on the semantics of what is pointed to.  In the case of Zephyr, some of these are
69//! intentionally thread safe (for example, things like `k_sem` which have the purpose of
70//! synchronizing between threads).  Others are not, and that is mirrored in Rust by whether or not
71//! `Clone` and/or `Sync` are implemented.  Please see the documentation of individual entities for
72//! details for that object.
73//!
74//! In general, methods on `Thing` will require `&mut self` if there is any state to manage.  Those
75//! that are built around synchronization primitives, however, will generally use `&self`.  In
76//! general, objects that implement `Clone` will use `&self` because there would be no benefit to
77//! mutable self when the object could be cloned.
78//!
79//! [`kobj_define!`]: crate::kobj_define
80//! [`init_once`]: StaticKernelObject::init_once
81
82#[cfg(CONFIG_RUST_ALLOC)]
83extern crate alloc;
84
85use core::{cell::UnsafeCell, mem};
86
87#[cfg(CONFIG_RUST_ALLOC)]
88use core::pin::Pin;
89
90#[cfg(CONFIG_RUST_ALLOC)]
91use alloc::boxed::Box;
92
93use crate::sync::atomic::{AtomicUsize, Ordering};
94
95/// ## Init/move safe objects
96///
97/// In Rust code, many language features are designed around Rust's "move semantics".  Because of
98/// the borrow checker, the Rust compiler has full knowledge of when it is safe to move an object in
99/// memory, as it will know that there are no references to it.
100///
101/// However, most kernel objects in Zephyr contain self-referential pointers to those objects.  The
102/// traditional way to handle this in Rust is to use `Pin`.  However, besides Pin being awkward to
103/// use, it is generally assumed that the underlying objects will be dynamically allocated.  It is
104/// desirable to allow as much functionality of Zephyr to be used without explicitly requiring
105/// alloc.
106///
107/// The original solution (Wrapped), used a type `Fixed` that either referenced a static, or
108/// contained a `Pin<Box<T>>` of the Zephyr kernel object.  This introduces overhead for both the
109/// enum as well as the actual reference itself.
110///
111/// Some simple benchmarking has determined that it is just as efficient, or even slightly more so,
112/// to represent each object instead as a `UnsafeCell` contaning the Zephyr object, and an atomic
113/// pointer that can be used to determine the state of the object.
114///
115/// This type is not intended for general use, but for the implementation of wrappers for Zephyr
116/// types that require non-move semantics.
117///
118/// # Safety
119///
120/// The Zephyr APIs require that once objects have been initialized, they are not moved in memory.
121/// To avoid the inconvenience of managing 'Pin' for most of these, we rely on a run-time detection
122/// both of initialization and non-movement.  It is fairly easy, as a user of an object in Rust to
123/// avoid moving it.  Generally, in an embedded system, objects will either live on the stack of a
124/// single persistent thread, or will be statically allocated.  Both of these cases will result in
125/// objects that don't move.  However, we want initialization to be able to happen on first _use_
126/// not when the constructor runs, because the semantics of most constructors invovles a move (even
127/// if that is often optimized away).
128///
129/// Note that this does not solve the case of objects that must not be moved even after the object
130/// has a single Rust reference (threads, and work queues, notably, or timers with active
131/// callbacks).
132///
133/// To do this, each object is paired with an Atomic pointer.  The pointer can exist in three state:
134/// - null: The object has not been initialized.  It is safe to move the object at this time.
135/// - pointer that equals the addres of the object itself.  Object has been initialized, and can be
136///   used.  It must not be moved.
137/// - pointer that doesn't match the object.  This indicates that the object was moved, and is
138///   invalid.  We treat this as a panic condition.
139pub struct ZephyrObject<T> {
140    state: AtomicUsize,
141    object: UnsafeCell<T>,
142}
143
144impl<T> ZephyrObject<T>
145where
146    ZephyrObject<T>: ObjectInit<T>,
147{
148    /// Construct a new Zephyr Object.
149    ///
150    /// The 'init' function will be given a reference to the object.  For objects that have
151    /// initialization parameters (specifically Semaphores), this can be used to hold those
152    /// parameters until the real init is called upon first use.
153    ///
154    /// The 'setup' function must not assume the address given to it will persist.  The object can
155    /// be freely moved by Rust until the 'init' call has been called, which happens on first use.
156    pub const fn new_raw() -> Self {
157        Self {
158            state: AtomicUsize::new(0),
159            // SAFETY: It is safe to assume Zephyr objects can be zero initialized before calling
160            // their init.  The atomic above will ensure that this is not used by any API other than
161            // the init call until it has been initialized.
162            object: UnsafeCell::new(unsafe { mem::zeroed() }),
163        }
164    }
165
166    /// Get a reference, _without initializing_ the item.
167    ///
168    /// This is useful during a const constructor to be able to stash values in the item.
169    pub const fn get_uninit(&self) -> *mut T {
170        self.object.get()
171    }
172
173    /// Get a reference to the underlying zephyr object, ensuring it has been initialized properly.
174    /// The method is unsafe, because the caller must ensure that the lifetime of `&self` is long
175    /// enough for the use of the raw pointer.
176    ///
177    /// # Safety
178    ///
179    /// If the object has not been initialized, It's 'init' method will be called.  If the object
180    /// has been moved since `init` was called, this will panic.  Otherwise, the caller must ensure
181    /// that the use of the returned pointer does not outlive the `&self`.
182    ///
183    /// The 'init' method will be called within a critical section, so should be careful to not
184    /// block, or take extra time.
185    pub unsafe fn get(&self) -> *mut T {
186        let addr = self.object.get();
187
188        // First, try reading the atomic relaxed.  This makes the common case of the object being
189        // initialized faster, and we can double check after.
190        match self.state.load(Ordering::Relaxed) {
191            // Uninitialized.  Falls through to the slower init case.
192            0 => (),
193            // Initialized, and object has not been moved.
194            ptr if ptr == addr as usize => return addr,
195            _ => {
196                // Object was moved after init.
197                panic!("Object moved after init");
198            }
199        }
200
201        // Perform the possible initialization within a critical section to avoid a race and double
202        // initialization.
203        critical_section::with(|_| {
204            // Reload, with Acquire ordering to see a determined value.
205            let state = self.state.load(Ordering::Acquire);
206
207            // If the address does match, an initialization got in before the critical section.
208            if state == addr as usize {
209                // Note, this returns from the closure, not the function, but this is ok, as the
210                // critical section result is the return result of the whole function.
211                return addr;
212            } else if state != 0 {
213                // Initialization got in, and then it was moved.  This shouldn't happen without
214                // unsafe code, but it is easy to detect.
215                panic!("Object moved after init");
216            }
217
218            // Perform the initialization.
219            <Self as ObjectInit<T>>::init(addr);
220
221            self.state.store(addr as usize, Ordering::Release);
222
223            addr
224        })
225    }
226}
227
228/// All `ZephyrObject`s must implement `ObjectInit` in order for first use to be able to initialize
229/// the object.
230pub trait ObjectInit<T> {
231    /// Initialize the object.
232    ///
233    /// This is called upon first use.  The address given may (and generally will) be different than
234    /// the initial address given to the `setup` call in the [`ZephyrObject::new_raw`] constructor.
235    /// After this is called, all subsequent calls to [`ZephyrObject::get`] will return the same
236    /// address, or panic.
237    fn init(item: *mut T);
238}
239
240// The kernel object itself must be wrapped in `UnsafeCell` in Rust.  This does several thing, but
241// the primary feature that we want to declare to the Rust compiler is that this item has "interior
242// mutability".  One impact will be that the default linker section will be writable, even though
243// the object will not be declared as mutable.  It also affects the compiler as it will avoid things
244// like aliasing and such on the data, as it will know that it is potentially mutable.  In our case,
245// the mutations happen from C code, so this is less important than the data being placed in the
246// proper section.  Many will have the link section overridden by the `kobj_define` macro.
247
248/// ## Old Wrapped objects
249///
250/// The wrapped objects was the original approach to managing Zephyr objects.
251///
252/// Define the Wrapping of a kernel object.
253///
254/// This trait defines the association between a static kernel object and the two associated Rust
255/// types: `StaticThing` and `Thing`.  In the general case: there should be:
256/// ```
257/// impl Wrapped for StaticKernelObject<kobj> {
258///     type T = Thing,
259///     type I = (),
260///     fn get_wrapped(&self, args: Self::I) -> Self::T {
261///         let ptr = self.value.get();
262///         // Initizlie the kobj using ptr and possible the args.
263///         Thing { ptr }
264///     }
265/// }
266/// ```
267pub trait Wrapped {
268    /// The wrapped type.  This is what `init_once()` on the StaticKernelObject will return after
269    /// initialization.
270    type T;
271
272    /// The wrapped type also requires zero or more initializers.  Which are represented by this
273    /// type.
274    type I;
275
276    /// Initialize this kernel object, and return the wrapped pointer.
277    fn get_wrapped(&self, args: Self::I) -> Self::T;
278}
279
280/// A state indicating an uninitialized kernel object.
281///
282/// This must be zero, as kernel objects will
283/// be represetned as zero-initialized memory.
284pub const KOBJ_UNINITIALIZED: usize = 0;
285
286/// A state indicating a kernel object that is being initialized.
287pub const KOBJ_INITING: usize = 1;
288
289/// A state indicating a kernel object that has completed initialization.  This also means that the
290/// take has been called.  And shouldn't be allowed additional times.
291pub const KOBJ_INITIALIZED: usize = 2;
292
293/// A kernel object represented statically in Rust code.
294///
295/// These should not be declared directly by the user, as they generally need linker decorations to
296/// be properly registered in Zephyr as kernel objects.  The object has the underlying Zephyr type
297/// T, and the wrapper type W.
298///
299/// Kernel objects will have their `StaticThing` implemented as `StaticKernelObject<kobj>` where
300/// `kobj` is the type of the underlying Zephyr object.  `Thing` will usually be a struct with a
301/// single field, which is a `*mut kobj`.
302///
303/// TODO: Can we avoid the public fields with a const new method?
304///
305/// TODO: Handling const-defined alignment for these.
306pub struct StaticKernelObject<T> {
307    #[allow(dead_code)]
308    /// The underlying zephyr kernel object.
309    pub value: UnsafeCell<T>,
310    /// Initialization status of this object.  Most objects will start uninitialized and be
311    /// initialized manually.
312    pub init: AtomicUsize,
313}
314
315impl<T> StaticKernelObject<T>
316where
317    StaticKernelObject<T>: Wrapped,
318{
319    /// Construct an empty of these objects, with the zephyr data zero-filled.
320    ///
321    /// # Safety
322    ///
323    /// This is safe in the sense that Zephyr we track the initialization, they start in the
324    /// uninitialized state, and the zero value of the initialize atomic indicates that it is
325    /// uninitialized.
326    pub const unsafe fn new() -> StaticKernelObject<T> {
327        StaticKernelObject {
328            value: unsafe { mem::zeroed() },
329            init: AtomicUsize::new(KOBJ_UNINITIALIZED),
330        }
331    }
332
333    /// Get the instance of the kernel object.
334    ///
335    /// Will return a single wrapped instance of this object.  This will invoke the initialization,
336    /// and return `Some<Wrapped>` for the wrapped containment type.
337    ///
338    /// If it is called an additional time, it will return None.
339    pub fn init_once(&self, args: <Self as Wrapped>::I) -> Option<<Self as Wrapped>::T> {
340        if self
341            .init
342            .compare_exchange(
343                KOBJ_UNINITIALIZED,
344                KOBJ_INITING,
345                Ordering::AcqRel,
346                Ordering::Acquire,
347            )
348            .is_err()
349        {
350            return None;
351        }
352        let result = self.get_wrapped(args);
353        self.init.store(KOBJ_INITIALIZED, Ordering::Release);
354        Some(result)
355    }
356}
357
358/// Objects that can be fixed or allocated.
359///
360/// When using Rust threads from userspace, the `kobj_define` declarations and the complexity behind
361/// it is required.  If all Rust use of kernel objects is from system threads, and dynamic memory is
362/// available, kernel objects can be freeallocated, as long as the allocations themselves are
363/// pinned.  This `Fixed` encapsulates both of these.
364pub enum Fixed<T> {
365    /// Objects that have been statically declared and just pointed to.
366    Static(*mut T),
367    /// Objects that are owned by the wrapper, and contained here.
368    #[cfg(CONFIG_RUST_ALLOC)]
369    Owned(Pin<Box<UnsafeCell<T>>>),
370}
371
372impl<T> Fixed<T> {
373    /// Get the raw pointer out of the fixed object.
374    ///
375    /// Returns the `*mut T` pointer held by this object.  It is either just the static pointer, or
376    /// the pointer outside of the unsafe cell holding the dynamic kernel object.
377    pub fn get(&self) -> *mut T {
378        match self {
379            Fixed::Static(ptr) => *ptr,
380            #[cfg(CONFIG_RUST_ALLOC)]
381            Fixed::Owned(item) => item.get(),
382        }
383    }
384
385    /// Construct a new fixed from an allocation.  Note that the object will not be fixed in memory,
386    /// until _after_ this returns, and it should not be initialized until then.
387    #[cfg(CONFIG_RUST_ALLOC)]
388    pub fn new(item: T) -> Fixed<T> {
389        Fixed::Owned(Box::pin(UnsafeCell::new(item)))
390    }
391}
392
393/// Declare a static kernel object.  This helps declaring static values of Zephyr objects.
394///
395/// This can typically be used as:
396/// ```
397/// kobj_define! {
398///     static A_MUTEX: StaticMutex;
399///     static MUTEX_ARRAY: [StaticMutex; 4];
400/// }
401/// ```
402#[macro_export]
403macro_rules! kobj_define {
404    ($v:vis static $name:ident: $type:tt; $($rest:tt)*) => {
405        $crate::_kobj_rule!($v, $name, $type);
406        $crate::kobj_define!($($rest)*);
407    };
408    ($v:vis static $name:ident: $type:tt<$size:ident>; $($rest:tt)*) => {
409        $crate::_kobj_rule!($v, $name, $type<$size>);
410        $crate::kobj_define!($($rest)*);
411    };
412    ($v:vis static $name:ident: $type:tt<$size:literal>; $($rest:tt)*) => {
413        $crate::_kobj_rule!($v, $name, $type<$size>);
414        $crate::kobj_define!($($rest)*);
415    };
416    ($v:vis static $name:ident: $type:tt<{$size:expr}>; $($rest:tt)*) => {
417        $crate::_kobj_rule!($v, $name, $type<{$size}>);
418        $crate::kobj_define!($($rest)*);
419    };
420    () => {};
421}
422
423#[doc(hidden)]
424#[macro_export]
425macro_rules! _kobj_rule {
426    // static NAME: StaticSemaphore;
427    ($v:vis, $name:ident, StaticSemaphore) => {
428        #[link_section = concat!("._k_sem.static.", stringify!($name), ".", file!(), line!())]
429        $v static $name: $crate::sys::sync::StaticSemaphore =
430            unsafe { ::core::mem::zeroed() };
431    };
432
433    // static NAMES: [StaticSemaphore; COUNT];
434    ($v:vis, $name:ident, [StaticSemaphore; $size:expr]) => {
435        #[link_section = concat!("._k_sem.static.", stringify!($name), ".", file!(), line!())]
436        $v static $name: [$crate::sys::sync::StaticSemaphore; $size] =
437            unsafe { ::core::mem::zeroed() };
438    };
439
440    // static NAME: StaticMutex
441    ($v:vis, $name:ident, StaticMutex) => {
442        #[link_section = concat!("._k_mutex.static.", stringify!($name), ".", file!(), line!())]
443        $v static $name: $crate::sys::sync::StaticMutex =
444            unsafe { $crate::sys::sync::StaticMutex::new() };
445    };
446
447    // static NAMES: [StaticMutex; COUNT];
448    ($v:vis, $name:ident, [StaticMutex; $size:expr]) => {
449        #[link_section = concat!("._k_mutex.static.", stringify!($name), ".", file!(), line!())]
450        $v static $name: [$crate::sys::sync::StaticMutex; $size] =
451            // This isn't Copy, intentionally, so initialize the whole thing with zerored memory.
452            // Relying on the atomic to be 0 for the uninitialized state.
453            // [$crate::sys::sync::StaticMutex::new(); $size];
454            unsafe { ::core::mem::zeroed() };
455    };
456
457    // static NAME: StaticCondvar;
458    ($v:vis, $name:ident, StaticCondvar) => {
459        #[link_section = concat!("._k_condvar.static.", stringify!($name), ".", file!(), line!())]
460        $v static $name: $crate::sys::sync::StaticCondvar =
461            unsafe { $crate::sys::sync::StaticCondvar::new() };
462    };
463
464    // static NAMES: [StaticCondvar; COUNT];
465    ($v:vis, $name:ident, [StaticCondvar; $size:expr]) => {
466        #[link_section = concat!("._k_condvar.static.", stringify!($name), ".", file!(), line!())]
467        $v static $name: [$crate::sys::sync::StaticCondvar; $size] =
468            // This isn't Copy, intentionally, so initialize the whole thing with zerored memory.
469            // Relying on the atomic to be 0 for the uninitialized state.
470            // [$crate::sys::sync::StaticMutex::new(); $size];
471            unsafe { ::core::mem::zeroed() };
472    };
473
474    // static THREAD: staticThread;
475    ($v:vis, $name:ident, StaticThread) => {
476        // Since the static object has an atomic that we assume is initialized, we cannot use the
477        // default linker section Zephyr uses for Thread, as that is uninitialized.  This will put
478        // it in .bss, where it is zero initialized.
479        $v static $name: $crate::sys::thread::StaticThread =
480            unsafe { ::core::mem::zeroed() };
481    };
482
483    // static THREAD: [staticThread; COUNT];
484    ($v:vis, $name:ident, [StaticThread; $size:expr]) => {
485        // Since the static object has an atomic that we assume is initialized, we cannot use the
486        // default linker section Zephyr uses for Thread, as that is uninitialized.  This will put
487        // it in .bss, where it is zero initialized.
488        $v static $name: [$crate::sys::thread::StaticThread; $size] =
489            unsafe { ::core::mem::zeroed() };
490    };
491
492    // Use indirection on stack initializers to handle some different cases in the Rust syntax.
493        ($v:vis, $name:ident, ThreadStack<$size:literal>) => {
494        $crate::_kobj_stack!($v, $name, $size);
495    };
496    ($v:vis, $name:ident, ThreadStack<$size:ident>) => {
497        $crate::_kobj_stack!($v, $name, $size);
498    };
499    ($v:vis, $name:ident, ThreadStack<{$size:expr}>) => {
500        $crate::_kobj_stack!($v, $name, $size);
501    };
502
503    // Array of stack object versions.
504    ($v:vis, $name:ident, [ThreadStack<$size:literal>; $asize:expr]) => {
505        $crate::_kobj_stack!($v, $name, $size, $asize);
506    };
507    ($v:vis, $name:ident, [ThreadStack<$size:ident>; $asize:expr]) => {
508        $crate::_kobj_stack!($v, $name, $size, $asize);
509    };
510    ($v:vis, $name:ident, [ThreadStack<{$size:expr}>; $asize:expr]) => {
511        $crate::_kobj_stack!($v, $name, $size, $asize);
512    };
513
514    // Queues.
515    ($v:vis, $name: ident, StaticQueue) => {
516        #[link_section = concat!("._k_queue.static.", stringify!($name), ".", file!(), line!())]
517        $v static $name: $crate::sys::queue::StaticQueue =
518            unsafe { ::core::mem::zeroed() };
519    };
520
521    ($v:vis, $name: ident, [StaticQueue; $size:expr]) => {
522        #[link_section = concat!("._k_queue.static.", stringify!($name), ".", file!(), line!())]
523        $v static $name: [$crate::sys::queue::StaticQueue; $size] =
524            unsafe { ::core::mem::zeroed() };
525    };
526}
527
528#[doc(hidden)]
529#[macro_export]
530macro_rules! _kobj_stack {
531    ($v:vis, $name: ident, $size:expr) => {
532        $crate::paste! {
533            // The actual stack itself goes into the no-init linker section.  We'll use the user_name,
534            // with _REAL appended, to indicate the real stack.
535            #[link_section = concat!(".noinit.", stringify!($name), ".", file!(), line!())]
536            $v static [< $name _REAL >]: $crate::sys::thread::RealStaticThreadStack<{$crate::sys::thread::stack_len($size)}> =
537                unsafe { ::core::mem::zeroed() };
538
539            // The proxy object used to ensure initialization is placed in initialized memory.
540            $v static $name: $crate::_export::KStaticThreadStack =
541                $crate::_export::KStaticThreadStack::new_from(&[< $name _REAL >]);
542        }
543    };
544
545    // This initializer needs to have the elements of the array initialized to fixed elements of the
546    // `RealStaticThreadStack`.  Unfortunately, methods such as [`each_ref`] on the array are not
547    // const and can't be used in a static initializer.  We could use a recursive macro definition
548    // to perform the initialization, but this would require the array size to only be an integer
549    // literal (constants aren't calculated until after macro expansion).  It may also be possible
550    // to write a constructor for the array as a const fn, which would greatly simplify the
551    // initialization here.
552    ($v:vis, $name: ident, $size:expr, $asize:expr) => {
553        $crate::paste! {
554            // The actual stack itself goes into the no-init linker section.  We'll use the user_name,
555            // with _REAL appended, to indicate the real stack.
556            #[link_section = concat!(".noinit.", stringify!($name), ".", file!(), line!())]
557            $v static [< $name _REAL >]:
558                [$crate::sys::thread::RealStaticThreadStack<{$crate::sys::thread::stack_len($size)}>; $asize] =
559                unsafe { ::core::mem::zeroed() };
560
561            $v static $name:
562                [$crate::_export::KStaticThreadStack; $asize] =
563                $crate::_export::KStaticThreadStack::new_from_array(&[< $name _REAL >]);
564        }
565    };
566}