diff --git a/doc/RTFM.md b/doc/RTFM.md index d217545f968416a33313dd7601d2fd4d68e474a3..5763cb7cf88086f3fe98be7e18843431653ff529 100644 --- a/doc/RTFM.md +++ b/doc/RTFM.md @@ -181,7 +181,40 @@ Exercises: Notice, under the Stack Resource Policy, there is an additional dispatch rule, on a tie among pending tasks priorties, the one with the oldest time for request has priority. This rule cannot be enforced directly by the NVIC. However, it can be shown that this restriction does not invalidate soundness, it only affects the response time calculation. -## The app! macro + +--- +## Overall design + +Code is split into three partitions, +- the generic `cortex-m-rtfm` library, +- the user code, and +- the *glue* code generated from the `app!` macro. + +--- +### `cortex-m-rtfm` library + +The library implements an *unsafe* `claim<T, R, F>` method, `T` being a referernce to the resource data (can be either `&` or `&mut`), `R` the return type, and `F: FnOnce(T, &mut Threshold) -> R` the closure to execute within the `claim`. Claim cannot be directy accessed from *safe* user code instead a *safe* API `claim/claim_mut` is offered by the generated code. (The API is implemented by a *trait* approach.) + +--- +### User code + +```rust +fn exti0( + t: &mut Threshold, + EXTI0::Resources { mut LOW, mut HIGH }: EXTI0::Resources, +) +``` + +`t` is the initial `Threshold`, used for the resource protection mechanism (as seen later the parameter will be opted out by the compiler in `--release` mode, yet been the logic behind the parameter will be taken into account.) + +`EXTI0::Resources { mut LOW, mut HIGH }: EXTI0::Resources` gives access to the the resources, `LOW` and `HIGH`. Technically, we *destruct* the given parameter (of type `EXTI0::Resource`) into its fields (`mut LOW`, `mut HIGH`). + + +Notice here the type `EXTI0::Resources` was not user defined, but rather generated by the `app!` macro. + +The `LOW`/`HIGH` arguments gives you *safe* access to the corresponding resources through the *safe* API (`claim/claim_mut`). + +### Generated code (app! macro) The procedural macro `app!` takes a sytem configuration, and performs the following: @@ -193,7 +226,9 @@ The procedural macro `app!` takes a sytem configuration, and performs the follow - task to interrupt bindings, and initialization code enabling corresponding interrupts - static memory allocation and initialization for Resources - Generation of structures for task parameters - - Interrupt handlers (calling the corresponding tasks) + - Interrupt entry points (calling the corresponding tasks) + + Procedural macros in Rust are executed before code generation (causing the argument AST to replaced by a new AST for the remainder of compilation). @@ -205,6 +240,45 @@ or Let us study the `nested` example in detail. +```rust +app! { + device: stm32f40x, + + resources: { + static LOW: u64 = 0; + static HIGH: u64 = 0; + }, + + tasks: { + EXTI0: { + path: exti0, + priority: 1, + resources: [LOW, HIGH], + }, + + EXTI1: { + path: exti1, + priority: 2, + resources: [LOW], + }, + + EXTI2: { + path: exti2, + priority: 3, + resources: [HIGH], + }, + }, +} +``` + + + + + + + +The intermediate AST defines the following `main` function. + ```rust fn main() { let init: fn(stm32f40x::Peripherals, init::Resources) = init; @@ -213,47 +287,191 @@ fn main() { init(stm32f40x::Peripherals::all(), init::Resources::new()); let nvic = &*stm32f40x::NVIC.get(); let prio_bits = stm32f40x::NVIC_PRIO_BITS; - let hw = ((1 << prio_bits) - 2u8) << (8 - prio_bits); - nvic.set_priority(stm32f40x::Interrupt::EXTI1, hw); - nvic.enable(stm32f40x::Interrupt::EXTI1); + let hw = ((1 << prio_bits) - 3u8) << (8 - prio_bits); + nvic.set_priority(stm32f40x::Interrupt::EXTI2, hw); + nvic.enable(stm32f40x::Interrupt::EXTI2); let prio_bits = stm32f40x::NVIC_PRIO_BITS; let hw = ((1 << prio_bits) - 1u8) << (8 - prio_bits); nvic.set_priority(stm32f40x::Interrupt::EXTI0, hw); nvic.enable(stm32f40x::Interrupt::EXTI0); let prio_bits = stm32f40x::NVIC_PRIO_BITS; - let hw = ((1 << prio_bits) - 1u8) << (8 - prio_bits); - nvic.set_priority(stm32f40x::Interrupt::EXTI3, hw); - nvic.enable(stm32f40x::Interrupt::EXTI3); - let prio_bits = stm32f40x::NVIC_PRIO_BITS; - let hw = ((1 << prio_bits) - 3u8) << (8 - prio_bits); - nvic.set_priority(stm32f40x::Interrupt::EXTI2, hw); - nvic.enable(stm32f40x::Interrupt::EXTI2); + let hw = ((1 << prio_bits) - 2u8) << (8 - prio_bits); + nvic.set_priority(stm32f40x::Interrupt::EXTI1, hw); + nvic.enable(stm32f40x::Interrupt::EXTI1); }); let idle: fn() -> ! = idle; idle(); } -fn init(_p: init::Peripherals, _r: init::Resources) {} -#[inline(never)] -fn idle() -> ! { - let mut stdout = hio::hstdout().unwrap(); - stdout - .write_fmt(::core::fmt::Arguments::new_v1( - &["Hello, world!\n"], - &match () { - () => [], - }, - )) - .unwrap(); - rtfm::bkpt(); - rtfm::set_pending(Interrupt::EXTI3); - loop { - rtfm::wfi(); +``` +Essentially, the generated code initates the peripheral and resource bindings in an `atomic` section (with the interrupts disabled). The code also sets the interrupt priorities and enables the interrupts. + + +```rust +pub struct _initResources<'a> { + pub LOW: &'a mut rtfm::Static<u64>, + pub HIGH: &'a mut rtfm::Static<u64>, +} +#[allow(unsafe_code)] +mod init { + pub use stm32f40x::Peripherals; + pub use _initResources as Resources; + #[allow(unsafe_code)] + impl<'a> Resources<'a> { + pub unsafe fn new() -> Self { + Resources { + LOW: ::rtfm::Static::ref_mut(&mut ::_LOW), + HIGH: ::rtfm::Static::ref_mut(&mut ::_HIGH), + } + } + } +} +static mut _HIGH: u64 = 0; +static mut _LOW: u64 = 0; + +mod _resource { + #[allow(non_camel_case_types)] + pub struct HIGH { + _0: (), + } + #[allow(unsafe_code)] + impl HIGH { + pub unsafe fn new() -> Self { + HIGH { _0: () } + } + } + #[allow(non_camel_case_types)] + pub struct LOW { + _0: (), + } + #[allow(unsafe_code)] + impl LOW { + pub unsafe fn new() -> Self { + LOW { _0: () } + } } } ``` +The allocation of memory for the system resources is done using (global) `static mut`, with resource names prepended by `_`. Resources can only by accessed from user code through the `Resource` wrapping, initialized at run time. + +In Rust a `mod` provides a *name space*, thus the statically allocated `HIGH` and `LOW` structs are accessed under the names `_resource::HIGH`, `_resource::LOW` respectively. + +Code is generated for binding the user API `RES::claim`/`RES::claim_mut` to the library implementation of `claim`. For `claim` the reference is passed as `rtfm::Static::ref_(&_HIGH)`, while for `claim_mut` the reference is passed as `rtfm::Static::ref_mut(&_HIGH)`. Recall here that `_HIGH` is the actual resource allocation. +Similarly code is generated for each resource. +```rust +unsafe impl rtfm::Resource for _resource::HIGH { + type Data = u64; + fn claim<R, F>(&self, t: &mut rtfm::Threshold, f: F) -> R + where + F: FnOnce(&rtfm::Static<u64>, &mut rtfm::Threshold) -> R, + { + unsafe { + rtfm::claim( + rtfm::Static::ref_(&_HIGH), + 3u8, + stm32f40x::NVIC_PRIO_BITS, + t, + f, + ) + } + } + fn claim_mut<R, F>(&mut self, t: &mut rtfm::Threshold, f: F) -> R + where + F: FnOnce(&mut rtfm::Static<u64>, &mut rtfm::Threshold) -> R, + { + unsafe { + rtfm::claim( + rtfm::Static::ref_mut(&mut _HIGH), + 3u8, + stm32f40x::NVIC_PRIO_BITS, + t, + f, + ) + } + } +} +``` +The `rtfm::Resource` *triat* and `rtfm::Static` type are given through the `rtfm_core` crate. + +```rust +pub unsafe trait Resource { + /// The data protected by the resource + type Data: Send; + + /// Claims the resource data for the span of the closure `f`. For the + /// duration of the closure other tasks that may access the resource data + /// are prevented from preempting the current task. + fn claim<R, F>(&self, t: &mut Threshold, f: F) -> R + where + F: FnOnce(&Static<Self::Data>, &mut Threshold) -> R; + + /// Mutable variant of `claim` + fn claim_mut<R, F>(&mut self, t: &mut Threshold, f: F) -> R + where + F: FnOnce(&mut Static<Self::Data>, &mut Threshold) -> R; + +} + +unsafe impl<T> Resource for Static<T> +where + T: Send, +{ + type Data = T; + + fn claim<R, F>(&self, t: &mut Threshold, f: F) -> R + where + F: FnOnce(&Static<Self::Data>, &mut Threshold) -> R, + { + f(self, t) + } + + fn claim_mut<R, F>(&mut self, t: &mut Threshold, f: F) -> R + where + F: FnOnce(&mut Static<Self::Data>, &mut Threshold) -> R, + { + f(self, t) + } +} + +/// Preemption threshold token +/// +/// The preemption threshold indicates the priority a task must have to preempt +/// the current context. For example a threshold of 2 indicates that only +/// interrupts / exceptions with a priority of 3 or greater can preempt the +/// current context +pub struct Threshold { + value: u8, + _not_send: PhantomData<*const ()>, +} + +impl Threshold { + /// Creates a new `Threshold` token + /// + /// This API is meant to be used to create abstractions and not to be + /// directly used by applications. + pub unsafe fn new(value: u8) -> Self { + Threshold { + value, + _not_send: PhantomData, + } + } + + /// Creates a `Threshold` token with maximum value + /// + /// This API is meant to be used to create abstractions and not to be + /// directly used by applications. + pub unsafe fn max() -> Self { + Self::new(u8::MAX) + } + + /// Returns the value of this `Threshold` token + pub fn value(&self) -> u8 { + self.value + } +} +```