Skip to content

Commit a745ecd

Browse files
committed
zephyr: object: Implement new ZephyrObject wrapper
Implement a new wrapping system for Zephyr Objects. Instead of trying to either use a static or a pinned-box, use an atomic to detect initialization, as well as improper moves. This allows for objects to have `const` constructors, allowing them to be declared as simple statics and just shared. Subsequent patches will implement this for various primitives. Signed-off-by: David Brown <[email protected]>
1 parent dd4c47d commit a745ecd

File tree

1 file changed

+147
-0
lines changed

1 file changed

+147
-0
lines changed

zephyr/src/object.rs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,149 @@ use alloc::boxed::Box;
9292

9393
use crate::sync::atomic::{AtomicUsize, Ordering};
9494

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.
139+
pub struct ZephyrObject<T> {
140+
state: AtomicUsize,
141+
object: UnsafeCell<T>,
142+
}
143+
144+
impl<T> ZephyrObject<T>
145+
where
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+
addr
222+
})
223+
}
224+
}
225+
226+
/// All `ZephyrObject`s must implement `ObjectInit` in order for first use to be able to initialize
227+
/// the object.
228+
pub trait ObjectInit<T> {
229+
/// Initialize the object.
230+
///
231+
/// This is called upon first use. The address given may (and generally will) be different than
232+
/// the initial address given to the `setup` call in the [`ZephyrObject::new`] constructor.
233+
/// After this is called, all subsequent calls to [`ZephyrObject::get`] will return the same
234+
/// address, or panic.
235+
fn init(item: *mut T);
236+
}
237+
95238
// The kernel object itself must be wrapped in `UnsafeCell` in Rust. This does several thing, but
96239
// the primary feature that we want to declare to the Rust compiler is that this item has "interior
97240
// mutability". One impact will be that the default linker section will be writable, even though
@@ -100,6 +243,10 @@ use crate::sync::atomic::{AtomicUsize, Ordering};
100243
// the mutations happen from C code, so this is less important than the data being placed in the
101244
// proper section. Many will have the link section overridden by the `kobj_define` macro.
102245

246+
/// ## Old Wrapped objects
247+
///
248+
/// The wrapped objects was the original approach to managing Zephyr objects.
249+
///
103250
/// Define the Wrapping of a kernel object.
104251
///
105252
/// This trait defines the association between a static kernel object and the two associated Rust

0 commit comments

Comments
 (0)