Skip to content

Commit 2755e20

Browse files
committed
zephyr: object: Improve docs about safety
Add comments to the object module describing the safety goals, and how we achieve them. Signed-off-by: David Brown <[email protected]>
1 parent 7ce61cb commit 2755e20

File tree

1 file changed

+100
-19
lines changed

1 file changed

+100
-19
lines changed

zephyr/src/object.rs

Lines changed: 100 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,94 @@
1313
//! There are also kernel objects that are synthesized as part of the build. Most notably, there
1414
//! are ones generated by the device tree.
1515
//!
16-
//! There are some funny rules about references and mutable references to memory that is
17-
//! inaccessible. Basically, it is never valid to have a reference to something that isn't
18-
//! accessible. However, we can have `*mut ktype` or `*const ktype`. In Rust, having multiple
19-
//! `*mut` pointers live is undefined behavior. Zephyr makes extensive use of shared mutable
20-
//! pointers (or sometimes immutable). We will not dereference these in Rust code, but merely pass
21-
//! them to APIs in Zephyr that require them.
16+
//! ## Safety
2217
//!
23-
//! Most kernel objects use mutable pointers, and it makes sense to require the wrapper structure
24-
//! to be mutable to these accesses. There a few cases, mostly generated ones that live in
25-
//! read-only memory, notably device instances, that need const pointers. These will be
26-
//! represented by a separate wrapper.
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.
2722
//!
28-
//! # Initialization tracking
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.
2927
//!
30-
//! The Kconfig `CONFIG_RUST_CHECK_KOBJ_INIT` enabled extra checking in Rust-based kernel objects.
31-
//! This will result in a panic if the objects are used before the underlying object has been
32-
//! initialized. The initialization must happen through the `StaticKernelObject::init_help`
33-
//! method.
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).
3434
//!
35-
//! TODO: Document how the wrappers work once we figure out how to implement them.
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
3681
3782
use core::{cell::UnsafeCell, mem};
3883

3984
use crate::sync::atomic::{AtomicUsize, Ordering};
4085

86+
// The kernel object itself must be wrapped in `UnsafeCell` in Rust. This does several thing, but
87+
// the primary feature that we want to declare to the Rust compiler is that this item has "interior
88+
// mutability". One impact will be that the default linker section will be writable, even though
89+
// the object will not be declared as mutable. It also affects the compiler as it will avoid things
90+
// like aliasing and such on the data, as it will know that it is potentially mutable. In our case,
91+
// the mutations happen from C code, so this is less important than the data being placed in the
92+
// proper section. Many will have the link section overridden by the `kobj_define` macro.
93+
4194
/// A kernel object represented statically in Rust code.
4295
///
4396
/// These should not be declared directly by the user, as they generally need linker decorations to
4497
/// be properly registered in Zephyr as kernel objects. The object has the underlying Zephyr type
4598
/// T, and the wrapper type W.
4699
///
100+
/// Kernel objects will have their `StaticThing` implemented as `StaticKernelObject<kobj>` where
101+
/// `kobj` is the type of the underlying Zephyr object. `Thing` will usually be a struct with a
102+
/// single field, which is a `*mut kobj`.
103+
///
47104
/// TODO: Can we avoid the public fields with a const new method?
48105
///
49106
/// TODO: Handling const-defined alignment for these.
@@ -56,9 +113,23 @@ pub struct StaticKernelObject<T> {
56113
pub init: AtomicUsize,
57114
}
58115

59-
/// Each can be wrapped appropriately. The wrapped type is the instance that holds the raw pointer.
116+
/// Define the Wrapping of a kernel object.
117+
///
118+
/// This trait defines the association between a static kernel object and the two associated Rust
119+
/// types: `StaticThing` and `Thing`. In the general case: there should be:
120+
/// ```
121+
/// impl Wrapped for StaticKernelObject<kobj> {
122+
/// type T = Thing,
123+
/// type I = (),
124+
/// fn get_wrapped(&self, args: Self::I) -> Self::T {
125+
/// let ptr = self.value.get();
126+
/// // Initizlie the kobj using ptr and possible the args.
127+
/// Thing { ptr }
128+
/// }
129+
/// }
130+
/// ```
60131
pub trait Wrapped {
61-
/// The wrapped type. This is what `take()` on the StaticKernelObject will return after
132+
/// The wrapped type. This is what `init_once()` on the StaticKernelObject will return after
62133
/// initialization.
63134
type T;
64135

@@ -88,7 +159,8 @@ where
88159
StaticKernelObject<T>: Wrapped,
89160
{
90161
/// Construct an empty of these objects, with the zephyr data zero-filled. This is safe in the
91-
/// sense that Zephyr we track the initialization, and they start in the uninitialized state.
162+
/// sense that Zephyr we track the initialization, they start in the uninitialized state, and
163+
/// the zero value of the initialize atomic indicates that it is uninitialized.
92164
pub const fn new() -> StaticKernelObject<T> {
93165
StaticKernelObject {
94166
value: unsafe { mem::zeroed() },
@@ -100,6 +172,8 @@ where
100172
///
101173
/// Will return a single wrapped instance of this object. This will invoke the initialization,
102174
/// and return `Some<Wrapped>` for the wrapped containment type.
175+
///
176+
/// If it is called an additional time, it will return None.
103177
pub fn init_once(&self, args: <Self as Wrapped>::I) -> Option<<Self as Wrapped>::T> {
104178
if let Err(_) = self.init.compare_exchange(
105179
KOBJ_UNINITIALIZED,
@@ -225,6 +299,13 @@ macro_rules! _kobj_stack {
225299
}
226300
};
227301

302+
// This initializer needs to have the elements of the array initialized to fixed elements of the
303+
// `RealStaticThreadStack`. Unfortunately, methods such as [`each_ref`] on the array are not
304+
// const and can't be used in a static initializer. We could use a recursive macro definition
305+
// to perform the initialization, but this would require the array size to only be an integer
306+
// literal (constants aren't calculated until after macro expansion). It may also be possible
307+
// to write a constructor for the array as a const fn, which would greatly simplify the
308+
// initialization here.
228309
($v:vis, $name: ident, $size:expr, $asize:expr) => {
229310
compile_error!("TODO: Stack initializer array");
230311
}

0 commit comments

Comments
 (0)