Skip to content
Snippets Groups Projects
Commit 584e9919 authored by Per's avatar Per
Browse files

RTFM implementation

parent 9a706b02
Branches
No related tags found
No related merge requests found
......@@ -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
}
}
```
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment