Skip to content

Commit

Permalink
kernel: static_init: more documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
bradjc committed Mar 12, 2020
1 parent 35af587 commit 2af7192
Showing 1 changed file with 84 additions and 30 deletions.
114 changes: 84 additions & 30 deletions kernel/src/common/utils.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Utility macros including `static_init!`.

/// Allocates a global array of static size to initialize data structures.
/// Allocates a statically-sized global array of memory and initializes the
/// memory for a particular data structure.
///
/// The global array is initially set to zero. When this macro is hit, it will
/// initialize the array to the value given and return a `&'static mut`
/// reference to it.
/// This macro creates the static buffer, ensures it is initialized to the
/// proper type, and then returns a `&'static mut` reference to it.
///
/// # Safety
///
Expand All @@ -21,54 +21,108 @@ macro_rules! static_init {
}};
}

/// Same as `static_init!()` but without initializing the buffer's contents.
/// Allocates a statically-sized global array of memory for data structures but
/// does not initialize the memory.
///
/// This macro creates the static buffer, and returns a
/// `StaticUninitializedBuffer` wrapper containing the buffer. The memory is
/// allocated, but it is guaranteed to be uninitialized inside of the wrapper.
///
/// Before the static buffer can be used it must be initialized. For example:
///
/// ```ignore
/// let mut static_buffer = static_buf!(<data structure type>);
/// let static_reference = static_buffer.initialize(<data structure>::new());
/// ```
///
/// `static_reference` is a `&'static mut` reference.
///
/// Separating the creation of the static buffer into its own macro is not
/// strictly necessary, but it allows for more flexibility in Rust when boards
/// are initialized and the static structures are being created. Since creating
/// and initializing static buffers requires knowing the particular types (and
/// their sizes), writing shared initialization code (in components for example)
/// where the types are unknown since they vary across boards is difficult. By
/// splitting buffer creating from initialization, creating shared components is
/// possible.
#[macro_export]
macro_rules! uninit_static_buf {
macro_rules! static_buf {
($T:ty) => {{
// Statically allocate a read-write buffer for the value, write our
// initial value into it (without dropping the initial zeros) and
// return a reference to it.
static mut BUF: $crate::common::utils::UninitBuf<$T> =
$crate::common::utils::UninitBuf::new();
$crate::UninitStaticBuf::new(&mut BUF)
static mut BUF: $crate::common::utils::UninitializedBuffer<$T> =
$crate::common::utils::UninitializedBuffer::new();
$crate::StaticUninitializedBuffer::new(&mut BUF)
}};
}

use core::mem::MaybeUninit;

/// A wrapper around a `MaybeUninit<T>` which is guaranteed to contain a
/// `MaybeUninit::uninit()` value.
/// The `UninitializedBuffer` type is designed to be statically allocated
/// as a global buffer to hold data structures in Tock. As a static, global
/// buffer the data structure can then be shared in the Tock kernel.
///
/// This type is implemented as a wrapper around a `MaybeUninit<T>` buffer.
/// To enforce that the global static buffer is initialized exactly once,
/// this wrapper type ensures that the underlying memory is uninitialized
/// so that an `UninitializedBuffer` does not contain an initialized value.
///
/// The only way to initialize this buffer is to create a
/// `StaticUninitializedBuffer`, pass the `UninitializedBuffer` to it, and call
/// `initialize()`. This structure ensures that:
///
/// 1. A static buffer is not initialized twice. Since the underlying memory is
/// owned by `UninitializedBuffer` nothing else can initialize it. Also, once
/// the memory is initialized via `StaticUninitializedBuffer.initialize()`,
/// the internal buffer is consumed and `initialize()` cannot be called
/// again.
///
/// 2. The static buffer is not used while uninitialized. Since the only way to
/// get the necessary `&'static mut T` is to call `initialize()`, the memory
/// is guaranteed to be initialized.
#[repr(transparent)]
pub struct UninitBuf<T>(MaybeUninit<T>);
pub struct UninitializedBuffer<T>(MaybeUninit<T>);

impl<T> UninitBuf<T> {
/// The only way to contruct an `UninitBuf` is via this function, which
/// initializes it to `MaybeUninit::uninit()`. This guarantees the
/// invariant that `UninitBuf` doesn't contain an initialized value.
impl<T> UninitializedBuffer<T> {
/// The only way to construct an `UninitializedBuffer` is via this function,
/// which initializes it to `MaybeUninit::uninit()`. This guarantees the
/// invariant that `UninitializedBuffer` does not contain an initialized
/// value.
pub const fn new() -> Self {
UninitBuf(MaybeUninit::uninit())
UninitializedBuffer(MaybeUninit::uninit())
}
}

/// A wrapper around a static mutable reference to an `UninitBuf`. This wrapper
/// can be consumed by initializing it to some value, obtaining a static
/// mutable reference to the inner type `T`.
pub struct UninitStaticBuf<T: 'static> {
buf: &'static mut UninitBuf<T>,
/// The `StaticUninitializedBuffer` type represents a statically allocated
/// buffer that can be converted to another type once it has been initialized.
/// Upon initialization, a static mutable reference is returned and the
/// `StaticUninitializedBuffer` is consumed.
///
/// This type is implemented as wrapper containing a static mutable reference to
/// an `UninitializedBuffer`. This guarantees that the memory at the reference
/// has not already been initialized.
///
/// `StaticUninitializedBuffer` provides one operation: `initialize()` that returns a
/// `&'static mut T` reference. This is the only way to get the reference, and
/// ensures that the underlying uninitialized buffer is properly initialized.
/// The wrapper is also consumed when `initialize()` is called, ensuring that
/// the underlying memory cannot be subsequently re-initialized.
pub struct StaticUninitializedBuffer<T: 'static> {
buf: &'static mut UninitializedBuffer<T>,
}

impl<T> UninitStaticBuf<T> {
impl<T> StaticUninitializedBuffer<T> {
/// This function is not intended to be called publicly. It's only meant to
/// be called within `uninit_static_buf!` macro, but Rust's visibility
/// rules require it to be public, so that the macro's body can be
/// instantiated.
pub fn new(buf: &'static mut UninitBuf<T>) -> Self {
/// be called within `static_buf!` macro, but Rust's visibility rules
/// require it to be public, so that the macro's body can be instantiated.
pub fn new(buf: &'static mut UninitializedBuffer<T>) -> Self {
Self { buf }
}

/// This function consumes an uninitialized static buffer, initializing it
/// to some value and returning a static mutable reference to it. This
/// allows for runtime initialization of `static` values that don't have a
/// This function consumes an uninitialized static buffer, initializes it
/// to some value, and returns a static mutable reference to it. This
/// allows for runtime initialization of `static` values that do not have a
/// `const` constructor.
pub unsafe fn initialize(self, value: T) -> &'static mut T {
self.buf.0.as_mut_ptr().write(value);
Expand Down

0 comments on commit 2af7192

Please sign in to comment.