Late resources
Some resources are initialized at runtime after the init
function returns.
It's important that these resources (static variables) are fully initialized
before tasks are allowed to run, that is they must be initialized while
interrupts are disabled.
The example below shows the kind of code that the framework generates to initialize late resources.
# #![allow(unused_variables)] #fn main() { #[rtfm::app(device = ..)] const APP: () = { // late resource static mut X: Thing = {}; #[init] fn init() -> init::LateResources { // .. init::LateResources { X: Thing::new(..), } } #[task(binds = UART0, resources = [X])] fn foo(c: foo::Context) { let x: &mut Thing = c.resources.X; x.frob(); // .. } // .. }; #}
The code generated by the framework looks like this:
fn init(c: init::Context) -> init::LateResources { // .. user code .. } fn foo(c: foo::Context) { // .. user code .. } // Public API pub mod init { pub struct LateResources { pub X: Thing, } // .. } pub mod foo { pub struct Resources<'a> { pub X: &'a mut Thing, } pub struct Context<'a> { pub resources: Resources<'a>, // .. } } /// Implementation details const APP: () = { // uninitialized static static mut X: MaybeUninit<Thing> = MaybeUninit::uninit(); #[no_mangle] unsafe fn main() -> ! { cortex_m::interrupt::disable(); // .. let late = init(..); // initialization of late resources X.write(late.X); cortex_m::interrupt::enable(); //~ compiler fence // exceptions, interrupts and tasks can preempt `main` at this point idle(..) } #[no_mangle] unsafe fn UART0() { foo(foo::Context { resources: foo::Resources { // `X` has been initialized at this point X: &mut *X.as_mut_ptr(), }, // .. }) } };
An important detail here is that interrupt::enable
behaves like a compiler
fence, which prevents the compiler from reordering the write to X
to after
interrupt::enable
. If the compiler were to do that kind of reordering there
would be a data race between that write and whatever operation foo
performs on
X
.
Architectures with more complex instruction pipelines may need a memory barrier
(atomic::fence
) instead of a compiler fence to fully flush the write operation
before interrupts are re-enabled. The ARM Cortex-M architecture doesn't need a
memory barrier in single-core context.