closes #32
closes #33
This commit is contained in:
Jorge Aparicio 2018-11-03 17:02:41 +01:00
parent 653338e799
commit c631049efc
154 changed files with 7538 additions and 3276 deletions

5
book/book.toml Normal file
View file

@ -0,0 +1,5 @@
[book]
authors = ["Jorge Aparicio"]
multilingual = false
src = "src"
title = "Real Time For the Masses"

16
book/src/SUMMARY.md Normal file
View file

@ -0,0 +1,16 @@
# Summary
[Preface](./preface.md)
- [RTFM by example](./by-example.md)
- [The `app` attribute](./by-example/app.md)
- [Resources](./by-example/resources.md)
- [Tasks](./by-example/tasks.md)
- [Timer queue](./by-example/timer-queue.md)
- [Singletons](./by-example/singletons.md)
- [Types, Send and Sync](./by-example/types-send-sync.md)
- [Starting a new project](./by-example/new.md)
- [Tips & tricks](./by-example/tips.md)
- [Under the hood](./internals.md)
- [Ceiling analysis](./internals/ceilings.md)
- [Task dispatcher](./internals/tasks.md)
- [Timer queue](./internals/timer-queue.md)

16
book/src/by-example.md Normal file
View file

@ -0,0 +1,16 @@
# RTFM by example
This part of the book introduces the Real Time For the Masses (RTFM) framework
to new users by walking them through examples of increasing complexity.
All examples in this part of the book can be found in the GitHub [repository] of
the project, and most of the examples can be run on QEMU so no special hardware
is required to follow along.
[repository]: https://github.com/japaric/cortex-m-rtfm
To run the examples on your laptop / PC you'll need the `qemu-system-arm`
program. Check [the embedded Rust book] for instructions on how to set up an
embedded development environment that includes QEMU.
[the embedded Rust book]: https://rust-embedded.github.io/book/intro/install.html

105
book/src/by-example/app.md Normal file
View file

@ -0,0 +1,105 @@
# The `app` attribute
This is the smallest possible RTFM application:
``` rust
{{#include ../../../examples/smallest.rs}}
```
All RTFM applications use the [`app`] attribute (`#[app(..)]`). This attribute
must be applied to a `const` item that contains items. The `app` attribute has
a mandatory `device` argument that takes a *path* as a value. This path must
point to a *device* crate generated using [`svd2rust`] **v0.14.x**. The `app`
attribute will expand into a suitable entry point so it's not required to use
the [`cortex_m_rt::entry`] attribute.
[`app`]: ../../api/cortex_m_rtfm_macros/attr.app.html
[`svd2rust`]: https://crates.io/crates/svd2rust
[`cortex_m_rt::entry`]: ../../api/cortex_m_rt_macros/attr.entry.html
> **ASIDE**: Some of you may be wondering why we are using a `const` item as a
> module and not a proper `mod` item. The reason is that using attributes on
> modules requires a feature gate, which requires a nightly toolchain. To make
> RTFM work on stable we use the `const` item instead. When more parts of macros
> 1.2 are stabilized we'll move from a `const` item to a `mod` item and
> eventually to a crate level attribute (`#![app]`).
## `init`
Within the pseudo-module the `app` attribute expects to find an initialization
function marked with the `init` attribute. This function must have signature
`[unsafe] fn()`.
This initialization function will be the first part of the application to run.
The `init` function will run *with interrupts disabled* and has exclusive access
to Cortex-M and device specific peripherals through the `core` and `device`
variables, which are injected in the scope of `init` by the `app` attribute. Not
all Cortex-M peripherals are available in `core` because the RTFM runtime takes
ownership of some of them -- for more details see the [`rtfm::Peripherals`]
struct.
`static mut` variables declared at the beginning of `init` will be transformed
into `&'static mut` references that are safe to access.
[`rtfm::Peripherals`]: ../../api/rtfm/struct.Peripherals.html
The example below shows the types of the `core` and `device` variables and
showcases safe access to a `static mut` variable.
``` rust
{{#include ../../../examples/init.rs}}
```
Running the example will print `init` to the console and then exit the QEMU
process.
``` console
$ cargo run --example init
{{#include ../../../ci/expected/init.run}}```
## `idle`
A function marked with the `idle` attribute can optionally appear in the
pseudo-module. This function is used as the special *idle task* and must have
signature `[unsafe] fn() - > !`.
When present, the runtime will execute the `idle` task after `init`. Unlike
`init`, `idle` will run *with interrupts enabled* and it's not allowed to return
so it runs forever.
When no `idle` function is declared, the runtime sets the [SLEEPONEXIT] bit and
then sends the microcontroller to sleep after running `init`.
[SLEEPONEXIT]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit
Like in `init`, `static mut` variables will be transformed into `&'static mut`
references that are safe to access.
The example below shows that `idle` runs after `init`.
``` rust
{{#include ../../../examples/idle.rs}}
```
``` console
$ cargo run --example idle
{{#include ../../../ci/expected/idle.run}}```
## `interrupt` / `exception`
Just like you would do with the `cortex-m-rt` crate you can use the `interrupt`
and `exception` attributes within the `app` pseudo-module to declare interrupt
and exception handlers. In RTFM, we refer to interrupt and exception handlers as
*hardware* tasks.
``` rust
{{#include ../../../examples/interrupt.rs}}
```
``` console
$ cargo run --example interrupt
{{#include ../../../ci/expected/interrupt.run}}```
So far all the RTFM applications we have seen look no different that the
applications one can write using only the `cortex-m-rt` crate. In the next
section we start introducing features unique to RTFM.

View file

@ -0,0 +1,67 @@
# Starting a new project
Now that you have learned about the main features of the RTFM framework you can
try it out on your hardware by following these instructions.
1. Instantiate the [`cortex-m-quickstart`] template.
[`cortex-m-quickstart`]: https://github.com/rust-embedded/cortex-m-quickstart#cortex-m-quickstart
``` console
$ # for example using `cargo-generate`
$ cargo generate \
--git https://github.com/rust-embedded/cortex-m-quickstart \
--name app
$ # follow the rest of the instructions
```
2. Add a device crate that was generated using [`svd2rust`] **v0.14.x**, or a
board support crate that depends on one such device crate as a dependency.
Make sure that the `rt` feature of the crate is enabled.
[`svd2rust`]: https://crates.io/crates/svd2rust
In this example, I'll use the [`lm3s6965`] device crate. This device crate
doesn't have an `rt` Cargo feature; that feature is always enabled.
[`lm3s6965`]: https://crates.io/crates/lm3s6965
This device crate provides a linker script with the memory layout of the target
device so `memory.x` and `build.rs` need to be removed.
``` console
$ cargo add lm3s6965 --vers 0.1.3
$ rm memory.x build.rs
```
3. Add the `cortex-m-rtfm` crate as a dependency and, if you need it, enable the
`timer-queue` feature.
``` console
$ cargo add cortex-m-rtfm --allow-prerelease --upgrade=none
```
4. Write your RTFM application.
Here I'll use the `init` example from the `cortex-m-rtfm` crate.
``` console
$ curl \
-L https://github.com/japaric/cortex-m-rtfm/raw/v0.4.0-beta.1/examples/init.rs \
> src/main.rs
```
That example depends on the `panic-semihosting` crate:
``` console
$ cargo add panic-semihosting
```
5. Build it, flash it and run it.
``` console
$ # NOTE: I have uncommented the `runner` option in `.cargo/config`
$ cargo run
{{#include ../../../ci/expected/init.run}}```

View file

@ -0,0 +1,119 @@
## Resources
One of the limitations of the attributes provided by the `cortex-m-rt` crate is
that sharing data (or peripherals) between interrupts, or between an interrupt
and the `entry` function, requires a `cortex_m::interrupt::Mutex`, which
*always* requires disabling *all* interrupts to access the data. Disabling all
the interrupts is not always required for memory safety but the compiler doesn't
have enough information to optimize the access to the shared data.
The `app` attribute has a full view of the application thus it can optimize
access to `static` variables. In RTFM we refer to the `static` variables
declared inside the `app` pseudo-module as *resources*. To access a resource the
context (`init`, `idle`, `interrupt` or `exception`) must first declare the
resource in the `resources` argument of its attribute.
In the example below two interrupt handlers access the same resource. No `Mutex`
is required in this case because the two handlers run at the same priority and
no preemption is possible. The `SHARED` resource can only be accessed by these
two handlers.
``` rust
{{#include ../../../examples/resource.rs}}
```
``` console
$ cargo run --example resource
{{#include ../../../ci/expected/resource.run}}```
## Priorities
The priority of each handler can be declared in the `interrupt` and `exception`
attributes. It's not possible to set the priority in any other way because the
runtime takes ownership of the `NVIC` peripheral; it's also not possible to
change the priority of a handler / task at runtime. Thanks to this restriction
the framework has knowledge about the *static* priorities of all interrupt and
exception handlers.
Interrupts and exceptions can have priorities in the range `1..=(1 <<
NVIC_PRIO_BITS)` where `NVIC_PRIO_BITS` is a constant defined in the `device`
crate. The `idle` task has a priority of `0`, the lowest priority.
Resources that are shared between handlers that run at different priorities
require critical sections for memory safety. The framework ensures that critical
sections are used but *only where required*: for example, no critical section is
required by the highest priority handler that has access to the resource.
The critical section API provided by the RTFM framework (see [`Mutex`]) is
based on dynamic priorities rather than on disabling interrupts. The consequence
is that these critical sections will prevent *some* handlers, including all the
ones that contend for the resource, from *starting* but will let higher priority
handlers, that don't contend for the resource, run.
[`Mutex`]: ../../api/rtfm/trait.Mutex.html
In the example below we have three interrupt handlers with priorities ranging
from one to three. The two handlers with the lower priorities contend for the
`SHARED` resource. The lowest priority handler needs to [`lock`] the
`SHARED` resource to access its data, whereas the mid priority handler can
directly access its data. The highest priority handler is free to preempt
the critical section created by the lowest priority handler.
[`lock`]: ../../api/rtfm/trait.Mutex.html#method.lock
``` rust
{{#include ../../../examples/lock.rs}}
```
``` console
$ cargo run --example lock
{{#include ../../../ci/expected/lock.run}}```
## Late resources
Unlike normal `static` variables, which need to be assigned an initial value
when declared, resources can be initialized at runtime. We refer to these
runtime initialized resources as *late resources*. Late resources are useful for
*moving* (as in transferring ownership) peripherals initialized in `init` into
interrupt and exception handlers.
Late resources are declared like normal resources but that are given an initial
value of `()` (the unit value). Late resources must be initialized at the end of
the `init` function using plain assignments (e.g. `FOO = 1`).
The example below uses late resources to stablish a lockless, one-way channel
between the `UART0` interrupt handler and the `idle` function. A single producer
single consumer [`Queue`] is used as the channel. The queue is split into
consumer and producer end points in `init` and then each end point is stored
in a different resource; `UART0` owns the producer resource and `idle` owns
the consumer resource.
[`Queue`]: ../../api/heapless/spsc/struct.Queue.html
``` rust
{{#include ../../../examples/late.rs}}
```
``` console
$ cargo run --example late
{{#include ../../../ci/expected/late.run}}```
## `static` resources
`static` variables can also be used as resources. Tasks can only get `&`
(shared) references to these resources but locks are never required to access
their data. You can think of `static` resources as plain `static` variables that
can be initialized at runtime and have better scoping rules: you can control
which tasks can access the variable, instead of the variable being visible to
all the functions in the scope it was declared in.
In the example below a key is loaded (or created) at runtime and then used from
two tasks that run at different priorities.
``` rust
{{#include ../../../examples/static.rs}}
```
``` console
$ cargo run --example static
{{#include ../../../ci/expected/static.run}}```

View file

@ -0,0 +1,26 @@
# Singletons
The `app` attribute is aware of [`owned-singleton`] crate and its [`Singleton`]
attribute. When this attribute is applied to one of the resources the runtime
will perform the `unsafe` initialization of the singleton for you, ensuring that
only a single instance of the singleton is ever created.
[`owned-singleton`]: ../../api/owned_singleton/index.html
[`Singleton`]: ../../api/owned_singleton_macros/attr.Singleton.html
Note that when using the `Singleton` attribute you'll need to have the
`owned_singleton` in your dependencies.
Below is an example that uses the `Singleton` attribute on a chunk of memory
and then uses the singleton instance as a fixed-size memory pool using one of
the [`alloc-singleton`] abstractions.
[`alloc-singleton`]: https://crates.io/crates/alloc-singleton
``` rust
{{#include ../../../examples/singleton.rs}}
```
``` console
$ cargo run --example singleton
{{#include ../../../ci/expected/singleton.run}}```

View file

@ -0,0 +1,63 @@
# Software tasks
RTFM treats interrupt and exception handlers as *hardware* tasks. Hardware tasks
are invoked by the hardware in response to events, like pressing a button. RTFM
also supports *software* tasks which can be spawned by the software from any
execution context.
Software tasks can also be assigned priorities and are dispatched from interrupt
handlers. RTFM requires that free interrupts are declared in an `extern` block
when using software tasks; these free interrupts will be used to dispatch the
software tasks. An advantage of software tasks over hardware tasks is that many
tasks can be mapped to a single interrupt handler.
Software tasks are declared by applying the `task` attribute to functions. To be
able to spawn a software task the name of the task must appear in the `spawn`
argument of the context attribute (`init`, `idle`, `interrupt`, etc.).
The example below showcases three software tasks that run at 2 different
priorities. The three tasks map to 2 interrupts handlers.
``` rust
{{#include ../../../examples/task.rs}}
```
``` console
$ cargo run --example task
{{#include ../../../ci/expected/task.run}}```
## Message passing
The other advantage of software tasks is that messages can be passed to these
tasks when spawning them. The type of the message payload must be specified in
the signature of the task handler.
The example below showcases three tasks, two of them expect a message.
``` rust
{{#include ../../../examples/message.rs}}
```
``` console
$ cargo run --example message
{{#include ../../../ci/expected/message.run}}```
## Capacity
Task dispatchers do *not* use any dynamic memory allocation. The memory required
to store messages is statically reserved. The framework will reserve enough
space for every context to be able to spawn each task at most once. This is a
sensible default but the "inbox" capacity of each task can be controlled using
the `capacicy` argument of the `task` attribute.
The example below sets the capacity of the software task `foo` to 4. If the
capacity is not specified then the second `spawn.foo` call in `UART0` would
fail.
``` rust
{{#include ../../../examples/capacity.rs}}
```
``` console
$ cargo run --example capacity
{{#include ../../../ci/expected/capacity.run}}```

View file

@ -0,0 +1,89 @@
# Timer queue
When the `timer-queue` feature is enabled the RTFM framework includes a *global
timer queue* that applications can use to *schedule* software tasks to run some
time in the future.
To be able to schedule a software task the name of the task must appear in the
`schedule` argument of the context attribute. When scheduling a task the
[`Instant`] at which the task should be executed must be passed as the first
argument of the `schedule` invocation.
[`Instant`]: ../../api/rtfm/struct.Instant.html
The RTFM runtime includes a monotonic, non-decreasing, 32-bit timer which can be
queried using the `Instant::now` constructor. A [`Duration`] can be added to
`Instant::now()` to obtain an `Instant` into the future. The monotonic timer is
disabled while `init` runs so `Instant::now()` always returns the value
`Instant(0 /* clock cycles */)`; the timer is enabled right before the
interrupts are re-enabled and `idle` is executed.
[`Duration`]: ../../api/rtfm/struct.Duration.html
The example below schedules two tasks from `init`: `foo` and `bar`. `foo` is
scheduled to run 8 million clock cycles in the future. Next, `bar` is scheduled
to run 4 million clock cycles in the future. `bar` runs before `foo` since it
was scheduled to run first.
> **IMPORTANT**: The examples that use the `schedule` API or the `Instant`
> abstraction will **not** properly work on QEMU because the Cortex-M cycle
> counter functionality has not been implemented in `qemu-system-arm`.
``` rust
{{#include ../../../examples/schedule.rs}}
```
Running the program on real hardware produces the following output in the console:
``` text
{{#include ../../../ci/expected/schedule.run}}
```
## Periodic tasks
Software tasks have access to the `Instant` at which they were scheduled to run
through the `scheduled` variable. This information and the `schedule` API can be
used to implement periodic tasks as shown in the example below.
``` rust
{{#include ../../../examples/periodic.rs}}
```
This is the output produced by the example. Note that there is zero drift /
jitter even though `schedule.foo` was invoked at the *end* of `foo`. Using
`Instant::now` instead of `scheduled` would have resulted in drift / jitter.
``` text
{{#include ../../../ci/expected/periodic.run}}
```
## Baseline
For the tasks scheduled from `init` we have exact information about their
`scheduled` time. For hardware tasks there's no `scheduled` time because these
tasks are asynchronous in nature. For hardware task the runtime provides a
`start` time, which indicates the time at which the task handler started
executing.
Note that `start` is **not** equal to the arrival time of the event that fired
the task. Depending on the priority of the task and the load of the system the
`start` time could be very far off from the event arrival time.
What do you think will be the value of `scheduled` for software tasks that are
*spawned* instead of scheduled? The answer is that spawned tasks inherit the
*baseline* time of the context that spawned it. The baseline of hardware tasks
is `start`, the baseline of software tasks is `scheduled` and the baseline of
`init` is `start = Instant(0)`. `idle` doesn't really have a baseline but tasks
spawned from it will use `Instant::now()` as their baseline time.
The example below showcases the different meanings of the *baseline*.
``` rust
{{#include ../../../examples/baseline.rs}}
```
Running the program on real hardware produces the following output in the console:
``` text
{{#include ../../../ci/expected/baseline.run}}
```

View file

@ -0,0 +1,43 @@
# Tips & tricks
## Running tasks from RAM
The main goal of moving the specification of RTFM applications to attributes in
RTFM v0.4.x was to allow inter-operation with other attributes. For example, the
`link_section` attribute can be applied to tasks to place them in RAM; this can
improve performance in some cases.
> **IMPORTANT**: In general, the `link_section`, `export_name` and `no_mangle`
> attributes are very powerful but also easy to misuse. Incorrectly using any of
> these attributes can cause undefined behavior; you should always prefer to use
> safe, higher level attributes around them like `cortex-m-rt`'s `interrupt` and
> `exception` attributes.
>
> In the particular case of RAM functions there's no
> safe abstraction for it in `cortex-m-rt` v0.6.5 but there's an [RFC] for
> adding a `ramfunc` attribute in a future release.
[RFC]: https://github.com/rust-embedded/cortex-m-rt/pull/100
The example below shows how to place the higher priority task, `bar`, in RAM.
``` rust
{{#include ../../../examples/ramfunc.rs}}
```
Running this program produces the expected output.
``` console
$ cargo run --example ramfunc
{{#include ../../../ci/expected/ramfunc.run}}```
One can look at the output of `cargo-nm` to confirm that `bar` ended in RAM
(`0x2000_0000`), whereas `foo` ended in Flash (`0x0000_0000`).
``` console
$ cargo nm --example ramfunc --release | grep ' foo::'
{{#include ../../../ci/expected/ramfunc.grep.foo}}```
``` console
$ cargo nm --example ramfunc --release | grep ' bar::'
{{#include ../../../ci/expected/ramfunc.grep.bar}}```

View file

@ -0,0 +1,60 @@
# Types, Send and Sync
The `app` attribute injects a context, a collection of variables, into every
function. All these variables have predictable, non-anonymous types so you can
write plain functions that take them as arguments.
The API reference specifies how these types are generated from the input. You
can also generate documentation for you binary crate (`cargo doc --bin <name>`);
in the documentation you'll find `Context` structs (e.g. `init::Context` and
`idle::Context`) whose fields represent the variables injected into each
function.
The example below shows the different types generates by the `app` attribute.
``` rust
{{#include ../../../examples/types.rs}}
```
## `Send`
[`Send`] is a marker trait for "types that can be transferred across thread
boundaries", according to its definition in `core`. In the context of RTFM the
`Send` trait is only required where it's possible to transfer a value between
tasks that run at *different* priorities. This occurs in a few places: in message
passing, in shared `static mut` resources and in the initialization of late
resources.
[`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html
The `app` attribute will enforce that `Send` is implemented where required so
you don't need to worry much about it. It's more important to know where you do
*not* need the `Send` trait: on types that are transferred between tasks that
run at the *same* priority. This occurs in two places: in message passing and in
shared `static mut` resources.
The example below shows where a type that doesn't implement `Send` can be used.
``` rust
{{#include ../../../examples/not-send.rs}}
```
## `Sync`
Similarly, [`Sync`] is a marker trait for "types for which it is safe to share
references between threads", according to its definition in `core`. In the
context of RTFM the `Sync` trait is only required where it's possible for two,
or more, tasks that run at different priority to hold a shared reference to a
resource. This only occurs with shared `static` resources.
[`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html
The `app` attribute will enforce that `Sync` is implemented where required but
it's important to know where the `Sync` bound is not required: in `static`
resources shared between tasks that run at the *same* priority.
The example below shows where a type that doesn't implement `Sync` can be used.
``` rust
{{#include ../../../examples/not-sync.rs}}
```

6
book/src/internals.md Normal file
View file

@ -0,0 +1,6 @@
# Under the hood
This section describes the internals of the RTFM framework at a *high level*.
Low level details like the parsing and code generation done by the procedural
macro (`#[app]`) will not be explained here. The focus will be the analysis of
the user specification and the data structures used by the runtime.

View file

@ -0,0 +1,3 @@
# Ceiling analysis
**TODO**

View file

@ -0,0 +1,3 @@
# Task dispatcher
**TODO**

View file

@ -0,0 +1,3 @@
# Timer queue
**TODO**

12
book/src/preface.md Normal file
View file

@ -0,0 +1,12 @@
<h1 align="center">Real Time For the Masses</h1>
<p align="center">A concurrency framework for building real time systems</p>
# Preface
This book contains user level documentation for the Real Time For the Masses
(RTFM) framework. The API reference can be found [here](../api/rtfm/index.html).
{{#include ../../README.md:5:53}}
{{#include ../../README.md:59:}}