pub struct ZephyrObject<T> { /* private fields */ }
Expand description
§Init/move safe objects
In Rust code, many language features are designed around Rust’s “move semantics”. Because of the borrow checker, the Rust compiler has full knowledge of when it is safe to move an object in memory, as it will know that there are no references to it.
However, most kernel objects in Zephyr contain self-referential pointers to those objects. The
traditional way to handle this in Rust is to use Pin
. However, besides Pin being awkward to
use, it is generally assumed that the underlying objects will be dynamically allocated. It is
desirable to allow as much functionality of Zephyr to be used without explicitly requiring
alloc.
The original solution (Wrapped), used a type Fixed
that either referenced a static, or
contained a Pin<Box<T>>
of the Zephyr kernel object. This introduces overhead for both the
enum as well as the actual reference itself.
Some simple benchmarking has determined that it is just as efficient, or even slightly more so,
to represent each object instead as a UnsafeCell
contaning the Zephyr object, and an atomic
pointer that can be used to determine the state of the object.
This type is not intended for general use, but for the implementation of wrappers for Zephyr types that require non-move semantics.
§Safety
The Zephyr APIs require that once objects have been initialized, they are not moved in memory. To avoid the inconvenience of managing ‘Pin’ for most of these, we rely on a run-time detection both of initialization and non-movement. It is fairly easy, as a user of an object in Rust to avoid moving it. Generally, in an embedded system, objects will either live on the stack of a single persistent thread, or will be statically allocated. Both of these cases will result in objects that don’t move. However, we want initialization to be able to happen on first use not when the constructor runs, because the semantics of most constructors invovles a move (even if that is often optimized away).
Note that this does not solve the case of objects that must not be moved even after the object has a single Rust reference (threads, and work queues, notably, or timers with active callbacks).
To do this, each object is paired with an Atomic pointer. The pointer can exist in three state:
- null: The object has not been initialized. It is safe to move the object at this time.
- pointer that equals the addres of the object itself. Object has been initialized, and can be used. It must not be moved.
- pointer that doesn’t match the object. This indicates that the object was moved, and is invalid. We treat this as a panic condition.
Implementations§
Source§impl<T> ZephyrObject<T>where
ZephyrObject<T>: ObjectInit<T>,
impl<T> ZephyrObject<T>where
ZephyrObject<T>: ObjectInit<T>,
Sourcepub const fn new_raw() -> Self
pub const fn new_raw() -> Self
Construct a new Zephyr Object.
The ‘init’ function will be given a reference to the object. For objects that have initialization parameters (specifically Semaphores), this can be used to hold those parameters until the real init is called upon first use.
The ‘setup’ function must not assume the address given to it will persist. The object can be freely moved by Rust until the ‘init’ call has been called, which happens on first use.
Sourcepub const fn get_uninit(&self) -> *mut T
pub const fn get_uninit(&self) -> *mut T
Get a reference, without initializing the item.
This is useful during a const constructor to be able to stash values in the item.
Sourcepub unsafe fn get(&self) -> *mut T
pub unsafe fn get(&self) -> *mut T
Get a reference to the underlying zephyr object, ensuring it has been initialized properly.
The method is unsafe, because the caller must ensure that the lifetime of &self
is long
enough for the use of the raw pointer.
§Safety
If the object has not been initialized, It’s ‘init’ method will be called. If the object
has been moved since init
was called, this will panic. Otherwise, the caller must ensure
that the use of the returned pointer does not outlive the &self
.
The ‘init’ method will be called within a critical section, so should be careful to not block, or take extra time.