@@ -275,20 +275,92 @@ As seen, the implmentation is fairly simple. `ceiling` here is the resource ceil
...
@@ -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 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)
-`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
- 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`?
- Accessing a *resource* from *safe* user code can only be done through the `Resource::claim/claim_mut` trait, calling the library `claim`
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.
- 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
pubstructThreshold{
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
lett=Threshold::new(0);// attempt to create a new Threshold
Procedural macros in Rust are executed before code generation (causing the argument AST to replaced by a new AST for the remainder of compilation).
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`.
The intermediate code (AST after expansion) can be exported by the `cargo` sub-command `export`.
```rust
>cargoexportexamplesnested>expanded.rs
>cargoexportexamplesnested>expanded.rs
```
or
or
```rust
>xargoexportexamplesnested>expanded.rs
>xargoexportexamplesnested>expanded.rs
```
Let us study the `nested` example in detail.
Let us study the `nested` example in detail.
```rust
```rust
...
@@ -322,12 +394,8 @@ app! {
...
@@ -322,12 +394,8 @@ app! {
}
}
```
```
---
### Auto generated `main`
The intermediate AST defines the following `main` function.
The intermediate AST defines the following `main` function.
```rust
```rust
...
@@ -354,14 +422,26 @@ fn main() {
...
@@ -354,14 +422,26 @@ fn main() {
idle();
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
staticmut_HIGH:u64=0;
staticmut_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
```rust
pubstruct_initResources<'a>{
pubstruct_initResources<'a>{
pubLOW:&'amutrtfm::Static<u64>,
pubLOW:&'amutrtfm::Static<u64>,
pubHIGH:&'amutrtfm::Static<u64>,
pubHIGH:&'amutrtfm::Static<u64>,
}
}
#[allow(unsafe_code)]
#[allow(unsafe_code)]
modinit{
modinit{
pubusestm32f40x::Peripherals;
pubusestm32f40x::Peripherals;
...
@@ -376,25 +456,29 @@ mod init {
...
@@ -376,25 +456,29 @@ mod init {
}
}
}
}
}
}
staticmut_HIGH:u64=0;
```
staticmut_LOW:u64=0;
---
### Auto generated `task` arguments
A generic resource abstraction is generated in `_resource`.
```rust
mod_resource{
mod_resource{
#[allow(non_camel_case_types)]
pubstructHIGH{
pubstructHIGH{
_0:(),
_0:(),
}
}
#[allow(unsafe_code)]
implHIGH{
implHIGH{
pubunsafefnnew()->Self{
pubunsafefnnew()->Self{
HIGH{_0:()}
HIGH{_0:()}
}
}
}
}
#[allow(non_camel_case_types)]
pubstructLOW{
pubstructLOW{
_0:(),
_0:(),
}
}
#[allow(unsafe_code)]
implLOW{
implLOW{
pubunsafefnnew()->Self{
pubunsafefnnew()->Self{
LOW{_0:()}
LOW{_0:()}
...
@@ -402,8 +486,6 @@ mod _resource {
...
@@ -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.
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.
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 {
...
@@ -420,7 +502,7 @@ unsafe impl rtfm::Resource for _resource::HIGH {
unsafe{
unsafe{
rtfm::claim(
rtfm::claim(
rtfm::Static::ref_(&_HIGH),
rtfm::Static::ref_(&_HIGH),
3u8,
3u8,// << computed ceiling value
stm32f40x::NVIC_PRIO_BITS,
stm32f40x::NVIC_PRIO_BITS,
t,
t,
f,
f,
...
@@ -434,7 +516,7 @@ unsafe impl rtfm::Resource for _resource::HIGH {
...
@@ -434,7 +516,7 @@ unsafe impl rtfm::Resource for _resource::HIGH {
unsafe{
unsafe{
rtfm::claim(
rtfm::claim(
rtfm::Static::ref_mut(&mut_HIGH),
rtfm::Static::ref_mut(&mut_HIGH),
3u8,
3u8,// << computed ceiling value
stm32f40x::NVIC_PRIO_BITS,
stm32f40x::NVIC_PRIO_BITS,
t,
t,
f,
f,
...
@@ -523,10 +605,100 @@ impl Threshold {
...
@@ -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.