diff --git a/doc/RTFM.md b/doc/RTFM.md index 309475f712f041cfca41bf7ca9265bcc5ed1215a..a1349724c1bdb87b42dbd77da481d986a0f1d9c0 100644 --- a/doc/RTFM.md +++ b/doc/RTFM.md @@ -275,20 +275,92 @@ As seen, the implmentation is fairly simple. `ceiling` here is the resource ceil - `ceiling == max_priority` => here we cannot protect the resource by setting `BASEPRI` (masking priorities), and instead use `atomic` (which executes the closure `|t| f(data, t)` with globally disabled interrupts ( `PRIMASK = true`) - `ceiling != max_priority` => here we store the current system ceiling, (`old = basepri::read())`, set the new system ceiling `basepri::write(hw)` execute the closure `ret = f(data, &mut Threshold::new(ceiling))`, restore the system ceiling, `basepri::write(old)` and return the result `ret`. The `PRIMASK` and `BASEPRI` regeisters are located in the `Private Peripheral Bus` memory region, which is `Strongly-ordered` (meaning that accesses are executed in program order). I.e. the next instruction following `basepri::write(hw)` (inside the `claim`) will be protected by the raised system ceiling. [Arm doc - memory barriers](https://static.docs.arm.com/dai0321/a/DAI0321A_programming_guide_memory_barriers_for_m_profile.pdf) -Race freness at this level can be argued from: +Race freeness at this level can be argued from: -- Each *resource* is associated a *ceiling according to SRP -- Accessing a *resource* from *safe* user code can only be done through the `Resource::claim/claim_mut` trait, calling the library `claim` +- Each *resource* is associated a *ceiling* according to SRP. The `app!` procedural macro computes the ceilings from the tasks defined and the resources (declared and) used. How do we ensure that a task cannot access a resource not declared used in the `app`? + The only resources accessible is those passed in the argument to the task (e.g., `EXTI0::Resources { mut LOW, mut HIGH }: EXTI0::Resources`). There is also no way in *safe* code to leak a reference to a resource through static (global memory) to another task. Notice though that is perfectly ok pass e.g., `&mut LOW` to a subroutine. In this case the sub routine will execute in task *context*. + + Another thing achieved here is that the Rust semantics for non-aliased mutability is ensured. (Essentiall a nested claim to the same resource would be illegal in Rust, since `claim` passes as mutable reference to the *inner* data). This cannot happen as `claim` takes a `mut T`. + +```rust + ... + LOW.claim_mut_new(b, t, |_low, b, t| { + rtfm::bkpt(); + LOW.claim_mut_new(b, t, |_high, _, _| { + rtfm::bkpt(); + }); + }); + ... +``` + would be rejected +``` +error[E0499]: cannot borrow `LOW` as mutable more than once at a time + --> examples/nested_new.rs:100:29 + | +100 | LOW.claim_mut_new(b, t, |_low, b, t| { + | --- ^^^^^^^^^^^^ second mutable borrow occurs here +``` + Trying to bluntly copy (clone) a resource handler will also fail. +```rust +let mut LOWC = LOW.clone(); + +error[E0599]: no method named `clone` found for type `_resource::LOW` in the current scope + --> examples/nested_new.rs:100:24 + | +100 | let mut LOWC = LOW.clone(); +``` +- Accessing a *resource* from *safe* user code can only be done through the `Resource::claim/claim_mut` trait, calling the generic library function `claim` +- The `claim` implementation together with the `NVIC`, `BASEPRI` and `PRIMASK` enforces the SRP dispatch policy. + + +However there is more to it: + +What if the user could fake (or alter) the `t` (Threshold). Well in that case the `claim` might give unprotected access. This is prevented by using an *opaque* data type `Threshold` in the `rtfm-core` lib. +```rust +pub struct Threshold { + value: u8, + _not_send: PhantomData<*const ()>, +} +``` +The `value` field is not accessible to the user directly (and the user cannot alter or create a new `Threshold`) and the API to `Threshold::new()` is *unsafe*, i.e., +```rust +... + *_t.value = 72; // attempt to fake Threshodld + let t = Threshold::new(0); // attempt to create a new Threshold +... +``` +will render: +```rust + Compiling cortex-m-rtfm v0.2.1 (file:///home/pln/course/nucleo-64-rtfm) +error[E0616]: field `value` of struct `rtfm::Threshold` is private + --> examples/nested_new.rs:135:6 + | +135 | *_t.value = 72; + | ^^^^^^^^ + | + = note: a method `value` also exists, perhaps you wish to call it + +error[E0133]: call to unsafe function requires unsafe function or block + --> examples/nested_new.rs:135:13 + | +135 | let t = Threshold::new(0); + | ^^^^^^^^^^^^^^^^^ call to unsafe function +``` + +## The generated code in detail Procedural macros in Rust are executed before code generation (causing the argument AST to replaced by a new AST for the remainder of compilation). The intermediate code (AST after expansion) can be exported by the `cargo` sub-command `export`. +```rust > cargo export examples nested > expanded.rs +``` or +```rust > xargo export examples nested > expanded.rs - +``` Let us study the `nested` example in detail. ```rust @@ -322,12 +394,8 @@ app! { } ``` - - - - - - +--- +### Auto generated `main` The intermediate AST defines the following `main` function. ```rust @@ -354,14 +422,26 @@ fn main() { idle(); } ``` -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. +Essentially, the generated code initates the peripheral and resource bindings in an `atomic` section (with the interrupts disabled). Besides first calling the user defined function `init`, the generated code also sets the interrupt priorities and enables the interrupts (tasks). +--- +### Allocition of resources +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. +```rust +static mut _HIGH: u64 = 0; +static mut _LOW: u64 = 0; +``` + +--- +### Auto generated `init` arguments +All resources and peripherals are passed to the user `init` as defined in the generated `_initResources`. The auto generated code implments a module `init` holding the resource handlers. ```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; @@ -376,25 +456,29 @@ mod init { } } } -static mut _HIGH: u64 = 0; -static mut _LOW: u64 = 0; +``` + +--- +### Auto generated `task` arguments + +A generic resource abstraction is generated in `_resource`. +```rust 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: () } @@ -402,8 +486,6 @@ mod _resource { } } ``` -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. @@ -420,7 +502,7 @@ unsafe impl rtfm::Resource for _resource::HIGH { unsafe { rtfm::claim( rtfm::Static::ref_(&_HIGH), - 3u8, + 3u8, // << computed ceiling value stm32f40x::NVIC_PRIO_BITS, t, f, @@ -434,7 +516,7 @@ unsafe impl rtfm::Resource for _resource::HIGH { unsafe { rtfm::claim( rtfm::Static::ref_mut(&mut _HIGH), - 3u8, + 3u8, // << computed ceiling value stm32f40x::NVIC_PRIO_BITS, t, f, @@ -523,10 +605,100 @@ impl Threshold { } } ``` +--- +### Interrupt entry points +Each task is mapped to a corresponding entry in the interrupt vector table. An entry point stub is generated for each task, calling the user defined code. Each taks is called with the exact set of resource handlers (and peripherals used), in the above example `EXTI0::Resources`. + +```rust +pub unsafe extern "C" fn _EXTI0() { + let f: fn(&mut rtfm::Threshold, EXTI0::Resources) = exti0; + f( + &mut if 1u8 == 1 << stm32f40x::NVIC_PRIO_BITS { + rtfm::Threshold::new(::core::u8::MAX) + } else { + rtfm::Threshold::new(1u8) + }, + EXTI0::Resources::new(), + ) +} + +mod EXTI0 { + pub struct Resources { + pub HIGH: ::_resource::HIGH, + pub LOW: ::_resource::LOW, + } + + impl Resources { + pub unsafe fn new() -> Self { + Resources { + HIGH: { ::_resource::HIGH::new() }, + LOW: { ::_resource::LOW::new() }, + } + } + } +} +``` +--- +## Performance +As seen there is quite some autogenerated and library code involved for the task and resource management. To our aid here is the Rust memory model allowing for zero cost abstractions. +The `exti0` task: +```rust +fn exti0( + t: &mut Threshold, + EXTI0::Resources { mut LOW, mut HIGH }: EXTI0::Resources, +) { + rtfm::bkpt(); + LOW.claim_mut(t, |_low, t| { + rtfm::bkpt(); + HIGH.claim_mut(t, |_high, _| { + rtfm::bkpt(); + }); + }); +} +``` +Amounts to the following assembly (including the interrupt entry code.) +```rust +Dump of assembler code for function nested_new::_EXTI0: + 0x080005a6 <+0>: movs r1, #224 ; 0xe0 +=> 0x080005a8 <+2>: bkpt 0x0000 + 0x080005aa <+4>: mrs r0, BASEPRI + 0x080005ae <+8>: movs r2, #208 ; 0xd0 + 0x080005b0 <+10>: msr BASEPRI, r1 + 0x080005b4 <+14>: bkpt 0x0000 + 0x080005b6 <+16>: mrs r1, BASEPRI + 0x080005ba <+20>: msr BASEPRI, r2 + 0x080005be <+24>: bkpt 0x0000 + 0x080005c0 <+26>: msr BASEPRI, r1 + 0x080005c4 <+30>: msr BASEPRI, r0 + 0x080005c8 <+34>: bx lr +``` +The worlds fastest preemtive scheduler for tasks with shared resources is at bay! (We challenge anyone to beat RTFM!) +# How low can you go +An observation here is that we read basepri in the inner claim +``` +0x080005b6 <+16>: mrs r1, BASEPRI +``` +though that we actually know that `BASEPRI` will have the value `r1` in this case. + +In an exprimental version of the RTFM implementation this observation has been explointed. +```rust +Dump of assembler code for function nested_new::_EXTI3: + 0x080005d0 <+0>: movs r1, #224 ; 0xe0 + 0x080005d2 <+2>: movs r2, #208 ; 0xd0 +=> 0x080005d4 <+4>: bkpt 0x0000 + 0x080005d6 <+6>: mrs r0, BASEPRI + 0x080005da <+10>: msr BASEPRI, r1 + 0x080005de <+14>: bkpt 0x0000 + 0x080005e0 <+16>: msr BASEPRI, r2 + 0x080005e4 <+20>: bkpt 0x0000 + 0x080005e6 <+22>: msr BASEPRI, r1 + 0x080005ea <+26>: msr BASEPRI, r0 + 0x080005ee <+30>: bx lr +```