mirror of
https://github.com/rtic-rs/rtic.git
synced 2024-11-24 04:32:52 +01:00
950 lines
26 KiB
Rust
950 lines
26 KiB
Rust
//! Real Time For the Masses (RTFM), a framework for building concurrent
|
|
//! applications, for ARM Cortex-M microcontrollers
|
|
//!
|
|
//! This crate is based on [the RTFM framework] created by the Embedded Systems
|
|
//! group at [Luleå University of Technology][ltu], led by Prof. Per Lindgren,
|
|
//! and uses a simplified version of the Stack Resource Policy as scheduling
|
|
//! policy (check the [references] for details).
|
|
//!
|
|
//! [the RTFM framework]: http://www.rtfm-lang.org/
|
|
//! [ltu]: https://www.ltu.se/?l=en
|
|
//! [per]: https://www.ltu.se/staff/p/pln-1.11258?l=en
|
|
//! [references]: ./index.html#references
|
|
//!
|
|
//! # Features
|
|
//!
|
|
//! - **Event triggered tasks** as the unit of concurrency.
|
|
//! - Supports prioritization of tasks and, thus, **preemptive multitasking**.
|
|
//! - **Efficient and data race free memory sharing** through fine grained *non
|
|
//! global* critical sections.
|
|
//! - **Deadlock free execution**, guaranteed at compile time.
|
|
//! - **Minimal scheduling overhead** as the scheduler has no "software
|
|
//! component"; the hardware does all the scheduling.
|
|
//! - **Highly efficient memory usage**. All the tasks share the call stack and
|
|
//! there's no hard dependency on a dynamic allocator.
|
|
//! - **All Cortex M3, M4 and M7 devices are fully supported**. M0(+) is
|
|
//! partially supported as the whole API is not available (due to missing
|
|
//! hardware features).
|
|
//! - The number of task priority levels is configurable at compile time through
|
|
//! the `P2` (4 levels), `P3` (8 levels), etc. Cargo features. The number of
|
|
//! priority levels supported by the hardware is device specific but this
|
|
//! crate defaults to 16 as that's the most common scenario.
|
|
//! - This task model is amenable to known WCET (Worst Case Execution Time)
|
|
//! analysis and scheduling analysis techniques. (Though we haven't yet
|
|
//! developed Rust friendly tooling for that.)
|
|
//!
|
|
//! # Requirements
|
|
//!
|
|
//! - Tasks must run to completion. That's it, tasks can't contain endless
|
|
//! loops.
|
|
//!
|
|
//! # Limitations
|
|
//!
|
|
//! - Task priorities must remain constant at runtime.
|
|
//!
|
|
//! # Dependencies
|
|
//!
|
|
//! - A device crate generated using [`svd2rust`] v0.7.x
|
|
//! - A `start` lang time: Vanilla `main` must be supported in binary crates.
|
|
//! You can use the [`cortex-m-rt`] crate to fulfill the requirement
|
|
//!
|
|
//! [`svd2rust`]: https://docs.rs/svd2rust/0.7.0/svd2rust/
|
|
//! [`cortex-m-rt`]: https://docs.rs/cortex-m-rt/0.1.1/cortex_m_rt/
|
|
//!
|
|
//! # Examples
|
|
//!
|
|
//! Ordered in increasing level of complexity:
|
|
//!
|
|
//! - [Zero tasks](./index.html#zero-tasks)
|
|
//! - [One task](./index.html#one-task)
|
|
//! - [Two "serial" tasks](./index.html#two-serial-tasks)
|
|
//! - [Preemptive multitasking](./index.html#preemptive-multitasking)
|
|
//! - [Peripherals as resources](./index.html#peripherals-as-resources)
|
|
//!
|
|
//! ## Zero tasks
|
|
//!
|
|
//! ``` ignore
|
|
//! #![feature(used)]
|
|
//! #![no_std]
|
|
//!
|
|
//! #[macro_use] // for the `hprintln!` macro
|
|
//! extern crate cortex_m;
|
|
//!
|
|
//! // before main initialization + `start` lang item
|
|
//! extern crate cortex_m_rt;
|
|
//!
|
|
//! #[macro_use] // for the `tasks!` macro
|
|
//! extern crate cortex_m_rtfm as rtfm;
|
|
//!
|
|
//! // device crate generated using svd2rust
|
|
//! extern crate stm32f30x;
|
|
//!
|
|
//! use rtfm::{C16, P0};
|
|
//!
|
|
//! // TASKS (None in this example)
|
|
//! tasks!(stm32f30x, {});
|
|
//!
|
|
//! // INITIALIZATION PHASE
|
|
//! fn init(_priority: P0, _ceiling: &C16) {
|
|
//! hprintln!("INIT");
|
|
//! }
|
|
//!
|
|
//! // IDLE LOOP
|
|
//! fn idle(_priority: P0) -> ! {
|
|
//! hprintln!("IDLE");
|
|
//!
|
|
//! // Sleep
|
|
//! loop {
|
|
//! rtfm::wfi();
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! Expected output:
|
|
//!
|
|
//! ``` text
|
|
//! INIT
|
|
//! IDLE
|
|
//! ```
|
|
//!
|
|
//! The `tasks!` macro overrides the `main` function and imposes the following
|
|
//! structure into your program:
|
|
//!
|
|
//! - `init`, the initialization phase, runs first. This function is executed
|
|
//! "atomically", in the sense that no task / interrupt can preempt it.
|
|
//!
|
|
//! - `idle`, a never ending function that runs after `init`.
|
|
//!
|
|
//! Note that both `init` and `idle` have priority 0, the lowest priority.
|
|
//!
|
|
//! # One task
|
|
//!
|
|
//! ``` ignore
|
|
//! #![feature(const_fn)]
|
|
//! #![feature(used)]
|
|
//! #![no_std]
|
|
//!
|
|
//! extern crate cortex_m_rt;
|
|
//! #[macro_use]
|
|
//! extern crate cortex_m_rtfm as rtfm;
|
|
//! extern crate stm32f30x;
|
|
//!
|
|
//! use stm32f30x::interrupt::Tim7;
|
|
//! use rtfm::{C16, Local, P0, P1};
|
|
//!
|
|
//! // INITIALIZATION PHASE
|
|
//! fn init(_priority: P0, _ceiling: &C16) {
|
|
//! // Configure TIM7 for periodic interrupts
|
|
//! // Configure GPIO for LED driving
|
|
//! }
|
|
//!
|
|
//! // IDLE LOOP
|
|
//! fn idle(_priority: P0) -> ! {
|
|
//! // Sleep
|
|
//! loop {
|
|
//! rtfm::wfi();
|
|
//! }
|
|
//! }
|
|
//!
|
|
//! // TASKS
|
|
//! tasks!(stm32f30x, {
|
|
//! periodic: Task {
|
|
//! interrupt: Tim7,
|
|
//! priority: P1,
|
|
//! enabled: true,
|
|
//! },
|
|
//! });
|
|
//!
|
|
//! fn periodic(mut task: Tim7, _priority: P1) {
|
|
//! // Task local data
|
|
//! static STATE: Local<bool, Tim7> = Local::new(false);
|
|
//!
|
|
//! let state = STATE.borrow_mut(&mut task);
|
|
//!
|
|
//! // Toggle state
|
|
//! *state = !*state;
|
|
//!
|
|
//! // Blink an LED
|
|
//! if *state {
|
|
//! LED.on();
|
|
//! } else {
|
|
//! LED.off();
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! Here we define a task named `periodic` and bind it to the `Tim7`
|
|
//! interrupt. The `periodic` task will run every time the `Tim7` interrupt
|
|
//! is triggered. We assign to this task a priority of 1 (`P1`); this is the
|
|
//! lowest priority that a task can have.
|
|
//!
|
|
//! We use the [`Local`](./struct.Local.html) abstraction to add state to the
|
|
//! task; this task local data will be preserved across runs of the `periodic`
|
|
//! task. Note that `STATE` is owned by the `periodic` task, in the sense that
|
|
//! no other task can access it; this is reflected in its type signature (the
|
|
//! `Tim7` type parameter).
|
|
//!
|
|
//! # Two "serial" tasks
|
|
//!
|
|
//! ``` ignore
|
|
//! #![feature(const_fn)]
|
|
//! #![feature(used)]
|
|
//! #![no_std]
|
|
//!
|
|
//! extern crate cortex_m_rt;
|
|
//! #[macro_use]
|
|
//! extern crate cortex_m_rtfm as rtfm;
|
|
//! extern crate stm32f30x;
|
|
//!
|
|
//! use core::cell::Cell;
|
|
//!
|
|
//! use stm32f30x::interrupt::{Tim6Dacunder, Tim7};
|
|
//! use rtfm::{C1, C16, P0, P1, Resource};
|
|
//!
|
|
//! // omitted: `idle`, `init`
|
|
//!
|
|
//! tasks!(stm32f30x, {
|
|
//! t1: Task {
|
|
//! interrupt: Tim6Dacunder,
|
|
//! priority: P1,
|
|
//! enabled: true,
|
|
//! },
|
|
//! t2: Task {
|
|
//! interrupt: Tim7,
|
|
//! priority: P1,
|
|
//! enabled: true,
|
|
//! },
|
|
//! });
|
|
//!
|
|
//! // Data shared between tasks `t1` and `t2`
|
|
//! static COUNTER: Resource<Cell<u32>, C1> = Resource::new(Cell::new(0));
|
|
//!
|
|
//! fn init(priority: P0, ceiling: &C16) {
|
|
//! // ..
|
|
//! }
|
|
//!
|
|
//! fn idle(priority: P0) -> ! {
|
|
//! // Sleep
|
|
//! loop {
|
|
//! rtfm::wfi();
|
|
//! }
|
|
//! }
|
|
//!
|
|
//! fn t1(_task: Tim6Dacunder, priority: P1) {
|
|
//! let ceiling = priority.as_ceiling();
|
|
//!
|
|
//! let counter = COUNTER.access(&priority, ceiling);
|
|
//!
|
|
//! counter.set(counter.get() + 1);
|
|
//! }
|
|
//!
|
|
//! fn t2(_task: Tim7, priority: P1) {
|
|
//! let ceiling = priority.as_ceiling();
|
|
//!
|
|
//! let counter = COUNTER.access(&priority, ceiling);
|
|
//!
|
|
//! counter.set(counter.get() + 2);
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! Here we declare two tasks, `t1` and `t2`; both with a priority of 1 (`P1`).
|
|
//! As both tasks have the same priority, we say that they are *serial* tasks in
|
|
//! the sense that `t1` can only run *after* `t2` is done and vice versa; i.e.
|
|
//! no preemption between them is possible.
|
|
//!
|
|
//! To share data between these two tasks, we use the
|
|
//! [`Resource`](./struct.Resource.html) abstraction. As the tasks can't preempt
|
|
//! each other, they can access the `COUNTER` resource using the zero cost
|
|
//! [`access`](./struct.Resource.html#method.access) method -- no
|
|
//! synchronization is required.
|
|
//!
|
|
//! `COUNTER` has an extra type parameter: `C1`. This is the *ceiling* of the
|
|
//! resource. For now suffices to say that the ceiling must be the maximum of
|
|
//! the priorities of all the tasks that access the resource -- in this case,
|
|
//! `C1 == max(P1, P1)`. If you try a smaller value like `C0`, you'll find out
|
|
//! that your program doesn't compile.
|
|
//!
|
|
//! # Preemptive multitasking
|
|
//!
|
|
//! ``` ignore
|
|
//! #![feature(const_fn)]
|
|
//! #![feature(used)]
|
|
//! #![no_std]
|
|
//!
|
|
//! extern crate cortex_m_rt;
|
|
//! #[macro_use]
|
|
//! extern crate cortex_m_rtfm as rtfm;
|
|
//! extern crate stm32f30x;
|
|
//!
|
|
//! use core::cell::Cell;
|
|
//!
|
|
//! use stm32f30x::interrupt::{Tim6Dacunder, Tim7};
|
|
//! use rtfm::{C1, C16, C2, P0, P1, P2, Resource};
|
|
//!
|
|
//! tasks!(stm32f30x, {
|
|
//! t1: Task {
|
|
//! interrupt: Tim6Dacunder,
|
|
//! priority: P1,
|
|
//! enabled: true,
|
|
//! },
|
|
//! t2: Task {
|
|
//! interrupt: Tim7,
|
|
//! priority: P2,
|
|
//! enabled: true,
|
|
//! },
|
|
//! });
|
|
//!
|
|
//! static COUNTER: Resource<Cell<u32>, C2> = Resource::new(Cell::new(0));
|
|
//!
|
|
//! fn init(priority: P0, ceiling: &C16) {
|
|
//! // ..
|
|
//! }
|
|
//!
|
|
//! fn idle(priority: P0) -> ! {
|
|
//! // Sleep
|
|
//! loop {
|
|
//! rtfm::wfi();
|
|
//! }
|
|
//! }
|
|
//!
|
|
//! fn t1(_task: Tim6Dacunder, priority: P1) {
|
|
//! // ..
|
|
//!
|
|
//! let ceiling: &C1 = priority.as_ceiling();
|
|
//!
|
|
//! ceiling.raise(
|
|
//! &COUNTER, |ceiling: &C2| {
|
|
//! let counter = COUNTER.access(&priority, ceiling);
|
|
//!
|
|
//! counter.set(counter.get() + 1);
|
|
//! }
|
|
//! );
|
|
//!
|
|
//! // ..
|
|
//! }
|
|
//!
|
|
//! fn t2(_task: Tim7, priority: P2) {
|
|
//! let ceiling = priority.as_ceiling();
|
|
//!
|
|
//! let counter = COUNTER.access(&priority, ceiling);
|
|
//!
|
|
//! counter.set(counter.get() + 2);
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! Now we have a variation of the previous example. Like before, `t1` has a
|
|
//! priority of 1 (`P1`) but `t2` now has a priority of 2 (`P2`). This means
|
|
//! that `t2` can preempt `t1` if a `Tim7` interrupt occurs while `t1` is
|
|
//! being executed.
|
|
//!
|
|
//! To avoid data races, `t1` must modify `COUNTER` in an atomic way; i.e. `t2`
|
|
//! most not preempt `t1` while `COUNTER` is being modified. This is
|
|
//! accomplished by [`raise`](./struct.C.html#method.raise)-ing the `ceiling`.
|
|
//! This creates a critical section, denoted by a closure; for whose execution,
|
|
//! `COUNTER` is accessible but `t2` is blocked from preempting `t1`.
|
|
//!
|
|
//! How `t2` accesses `COUNTER` remains unchanged. Since `t1` can't preempt `t2`
|
|
//! due to the differences in priority; no critical section is needed in `t2`.
|
|
//!
|
|
//! Note that the ceiling of `COUNTER` had to be changed to `C2`. This is
|
|
//! required because the ceiling must be the maximum between `P1` and `P2`.
|
|
//!
|
|
//! Finally, it should be noted that the critical section in `t1` will only
|
|
//! block tasks with a priority of 2 or lower. This is exactly what the ceiling
|
|
//! represents: it's the "bar" that a task priority must pass in order to be
|
|
//! able to preempt the current task / critical section. Note that a task with
|
|
//! e.g. a priority of 3 (`P3`) effectively imposes a ceiling of 3 (`C3`)
|
|
//! because only other task with a priority of 4 or greater can preempt it.
|
|
//!
|
|
//! # Peripherals as resources
|
|
//!
|
|
//! ``` ignore
|
|
//! #![feature(const_fn)]
|
|
//! #![feature(used)]
|
|
//! #![no_std]
|
|
//!
|
|
//! extern crate cortex_m_rt;
|
|
//! #[macro_use]
|
|
//! extern crate cortex_m_rtfm as rtfm;
|
|
//! extern crate stm32f30x;
|
|
//!
|
|
//! use rtfm::{C0, C16, P0, Peripheral};
|
|
//!
|
|
//! peripherals!(stm32f30x, {
|
|
//! GPIOA: Peripheral {
|
|
//! register_block: Gpioa,
|
|
//! ceiling: C0,
|
|
//! },
|
|
//! RCC: Peripheral {
|
|
//! register_block: Rcc,
|
|
//! ceiling: C0,
|
|
//! },
|
|
//! });
|
|
//!
|
|
//! tasks!(stm32f30x, {});
|
|
//!
|
|
//! fn init(priority: P0, ceiling: &C16) {
|
|
//! let gpioa = GPIOA.access(&priority, &ceiling);
|
|
//! let rcc = RCC.access(&priority, &ceiling);
|
|
//!
|
|
//! // ..
|
|
//! }
|
|
//!
|
|
//! fn idle(_priority: P0) -> ! {
|
|
//! // Sleep
|
|
//! loop {
|
|
//! rtfm::wfi();
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! Peripherals are global resources too and as such they can be protected in
|
|
//! the same way as `Resource`s using the
|
|
//! [`Peripheral`](./struct.Peripheral.html) abstraction.
|
|
//!
|
|
//! `Peripheral` and `Resource` has pretty much the same API except that
|
|
//! `Peripheral` instances must be declared using the
|
|
//! [`peripherals!`](./macro.peripherals.html) macro.
|
|
//!
|
|
//! # References
|
|
//!
|
|
//! - Baker, T. P. (1991). Stack-based scheduling of realtime processes.
|
|
//! *Real-Time Systems*, 3(1), 67-99.
|
|
//!
|
|
//! > The original Stack Resource Policy paper. [PDF].
|
|
//!
|
|
//! [PDF]: http://www.cs.fsu.edu/~baker/papers/mstacks3.pdf
|
|
//!
|
|
//! - Eriksson, J., Häggström, F., Aittamaa, S., Kruglyak, A., & Lindgren, P.
|
|
//! (2013, June). Real-time for the masses, step 1: Programming API and static
|
|
//! priority SRP kernel primitives. In Industrial Embedded Systems (SIES),
|
|
//! 2013 8th IEEE International Symposium on (pp. 110-113). IEEE.
|
|
//!
|
|
//! > A description of the RTFM task and resource model. [PDF]
|
|
//!
|
|
//! [PDF]: http://www.diva-portal.org/smash/get/diva2:1005680/FULLTEXT01.pdf
|
|
|
|
#![deny(missing_docs)]
|
|
#![deny(warnings)]
|
|
#![feature(asm)]
|
|
#![feature(const_fn)]
|
|
#![no_std]
|
|
|
|
extern crate cortex_m;
|
|
extern crate static_ref;
|
|
extern crate typenum;
|
|
|
|
use core::cell::UnsafeCell;
|
|
use core::marker::PhantomData;
|
|
use core::{mem, ptr};
|
|
|
|
use cortex_m::ctxt::Context;
|
|
use cortex_m::interrupt::Nr;
|
|
#[cfg(not(thumbv6m))]
|
|
use cortex_m::register::{basepri, basepri_max};
|
|
use static_ref::Ref;
|
|
use typenum::{Cmp, Greater, U0, Unsigned};
|
|
#[cfg(not(thumbv6m))]
|
|
use typenum::Less;
|
|
|
|
pub use cortex_m::asm::{bkpt, wfi};
|
|
|
|
#[doc(hidden)]
|
|
pub use cortex_m::peripheral::NVIC as _NVIC;
|
|
|
|
/// Compiler barrier
|
|
macro_rules! barrier {
|
|
() => {
|
|
asm!(""
|
|
:
|
|
:
|
|
: "memory"
|
|
: "volatile");
|
|
}
|
|
}
|
|
|
|
/// Task local data
|
|
///
|
|
/// This data can only be accessed by the task `T`
|
|
pub struct Local<D, T> {
|
|
_task: PhantomData<T>,
|
|
data: UnsafeCell<D>,
|
|
}
|
|
|
|
impl<T, TASK> Local<T, TASK> {
|
|
/// Creates a task local variable with some initial `value`
|
|
pub const fn new(value: T) -> Self {
|
|
Local {
|
|
_task: PhantomData,
|
|
data: UnsafeCell::new(value),
|
|
}
|
|
}
|
|
|
|
/// Borrows the task local data for the duration of the task
|
|
pub fn borrow<'task>(&'static self, _task: &'task TASK) -> &'task T {
|
|
unsafe { &*self.data.get() }
|
|
}
|
|
|
|
/// Mutably borrows the task local data for the duration of the task
|
|
pub fn borrow_mut<'task>(
|
|
&'static self,
|
|
_task: &'task mut TASK,
|
|
) -> &'task mut T {
|
|
unsafe { &mut *self.data.get() }
|
|
}
|
|
}
|
|
|
|
unsafe impl<T, TASK> Sync for Local<T, TASK> {}
|
|
|
|
/// A resource with ceiling `C`
|
|
pub struct Resource<T, C> {
|
|
_ceiling: PhantomData<C>,
|
|
data: UnsafeCell<T>,
|
|
}
|
|
|
|
impl<T, RC> Resource<T, C<RC>>
|
|
where
|
|
RC: GreaterThanOrEqual<U0>,
|
|
RC: LessThanOrEqual<UMAX>,
|
|
{
|
|
/// Creates a new resource
|
|
pub const fn new(data: T) -> Self {
|
|
Resource {
|
|
_ceiling: PhantomData,
|
|
data: UnsafeCell::new(data),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T, RC> Resource<T, C<RC>> {
|
|
/// Grants data race free and deadlock free access to the resource data
|
|
///
|
|
/// This operation is zero cost and doesn't impose any additional blocking.
|
|
///
|
|
/// # Requirements
|
|
///
|
|
/// To access the resource data these conditions must be met:
|
|
///
|
|
/// - The resource ceiling must be greater than or equal to the task
|
|
/// priority
|
|
/// - The system ceiling must be greater than or equal to the resource
|
|
/// ceiling
|
|
pub fn access<'cs, TP, SC>(
|
|
&'static self,
|
|
_priority: &P<TP>,
|
|
_current_ceiling: &'cs C<SC>,
|
|
) -> Ref<'cs, T>
|
|
where
|
|
RC: GreaterThanOrEqual<TP>,
|
|
SC: GreaterThanOrEqual<RC>,
|
|
{
|
|
unsafe { Ref::new(&*self.data.get()) }
|
|
}
|
|
}
|
|
|
|
unsafe impl<T, C> Sync for Resource<T, C> {}
|
|
|
|
/// A hardware peripheral as a resource
|
|
///
|
|
/// To assign a ceiling to a peripheral, use the
|
|
/// [`peripherals!`](./macro.peripherals.html) macro
|
|
pub struct Peripheral<P, PC>
|
|
where
|
|
P: 'static,
|
|
{
|
|
peripheral: cortex_m::peripheral::Peripheral<P>,
|
|
_ceiling: PhantomData<PC>,
|
|
}
|
|
|
|
impl<P, CEILING> Peripheral<P, C<CEILING>>
|
|
where
|
|
CEILING: GreaterThanOrEqual<U0>,
|
|
CEILING: LessThanOrEqual<UMAX>,
|
|
{
|
|
#[doc(hidden)]
|
|
pub const unsafe fn _new(peripheral: cortex_m::peripheral::Peripheral<P>,)
|
|
-> Self {
|
|
Peripheral {
|
|
_ceiling: PhantomData,
|
|
peripheral: peripheral,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Periph, RC> Peripheral<Periph, C<RC>> {
|
|
/// See [Resource.access](./struct.Resource.html#method.access)
|
|
pub fn access<'cs, TP, SC>(
|
|
&'static self,
|
|
_priority: &P<TP>,
|
|
_system_ceiling: &'cs C<SC>,
|
|
) -> Ref<'cs, Periph>
|
|
where
|
|
RC: GreaterThanOrEqual<TP>,
|
|
SC: GreaterThanOrEqual<RC>,
|
|
{
|
|
unsafe { Ref::new(&*self.peripheral.get()) }
|
|
}
|
|
}
|
|
|
|
unsafe impl<T, C> Sync for Peripheral<T, C> {}
|
|
|
|
/// Runs the closure `f` "atomically"
|
|
///
|
|
/// No task can preempt the execution of the closure
|
|
pub fn atomic<R, F>(f: F) -> R
|
|
where
|
|
F: FnOnce(&CMAX) -> R,
|
|
{
|
|
let primask = ::cortex_m::register::primask::read();
|
|
::cortex_m::interrupt::disable();
|
|
|
|
let r = f(&C { _marker: PhantomData });
|
|
|
|
// If the interrupts were active before our `disable` call, then re-enable
|
|
// them. Otherwise, keep them disabled
|
|
if primask.is_active() {
|
|
::cortex_m::interrupt::enable();
|
|
}
|
|
|
|
r
|
|
}
|
|
|
|
/// Disables a `task`
|
|
///
|
|
/// The task won't run even if the underlying interrupt is raised
|
|
pub fn disable<T, TP>(_task: fn(T, P<TP>))
|
|
where
|
|
T: Context + Nr,
|
|
{
|
|
// NOTE(safe) zero sized type
|
|
let _task = unsafe { ptr::read(0x0 as *const T) };
|
|
|
|
// NOTE(safe) atomic write
|
|
unsafe { (*_NVIC.get()).disable(_task) }
|
|
}
|
|
|
|
/// Enables a `task`
|
|
pub fn enable<T, TP>(_task: fn(T, P<TP>))
|
|
where
|
|
T: Context + Nr,
|
|
{
|
|
// NOTE(safe) zero sized type
|
|
let _task = unsafe { ptr::read(0x0 as *const T) };
|
|
|
|
// NOTE(safe) atomic write
|
|
unsafe { (*_NVIC.get()).enable(_task) }
|
|
}
|
|
|
|
/// Converts a shifted hardware priority into a logical priority
|
|
pub fn hw2logical(hw: u8) -> u8 {
|
|
(1 << PRIORITY_BITS) - (hw >> (8 - PRIORITY_BITS))
|
|
}
|
|
|
|
/// Converts a logical priority into a shifted hardware priority, as used by the
|
|
/// NVIC and the BASEPRI register
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This function panics if `logical` is outside the closed range
|
|
/// `[1, 1 << PRIORITY_BITS]`. Where `PRIORITY_BITS` is the number of priority
|
|
/// bits used by the device specific NVIC implementation.
|
|
pub fn logical2hw(logical: u8) -> u8 {
|
|
assert!(logical >= 1 && logical <= (1 << PRIORITY_BITS));
|
|
|
|
((1 << PRIORITY_BITS) - logical) << (8 - PRIORITY_BITS)
|
|
}
|
|
|
|
/// Requests the execution of a `task`
|
|
pub fn request<T, TP>(_task: fn(T, P<TP>))
|
|
where
|
|
T: Context + Nr,
|
|
{
|
|
let nvic = unsafe { &*_NVIC.get() };
|
|
|
|
match () {
|
|
#[cfg(debug_assertions)]
|
|
() => {
|
|
// NOTE(safe) zero sized type
|
|
let task = unsafe { core::ptr::read(0x0 as *const T) };
|
|
// NOTE(safe) atomic read
|
|
assert!(!nvic.is_pending(task),
|
|
"Task is already in the pending state");
|
|
}
|
|
#[cfg(not(debug_assertions))]
|
|
() => {}
|
|
}
|
|
|
|
// NOTE(safe) zero sized type
|
|
let task = unsafe { core::ptr::read(0x0 as *const T) };
|
|
|
|
// NOTE(safe) atomic write
|
|
nvic.set_pending(task);
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
pub fn _validate_priority<TP>(_: &P<TP>)
|
|
where
|
|
TP: Cmp<U0, Output = Greater> + LessThanOrEqual<UMAX>,
|
|
{
|
|
}
|
|
|
|
/// A type-level ceiling
|
|
pub struct C<T> {
|
|
_marker: PhantomData<T>,
|
|
}
|
|
|
|
impl<SC> C<SC> {
|
|
/// Raises the system ceiling to match the `resource` ceiling
|
|
#[cfg(not(thumbv6m))]
|
|
pub fn raise<RC, RES, R, F>(&self, _resource: &'static RES, f: F) -> R
|
|
where
|
|
RES: ResourceLike<Ceiling = RC>,
|
|
RC: Cmp<SC, Output = Greater> + Cmp<UMAX, Output = Less> + Unsigned,
|
|
F: FnOnce(&C<RC>) -> R,
|
|
{
|
|
unsafe {
|
|
let old_basepri = basepri::read();
|
|
basepri_max::write(logical2hw(RC::to_u8()));
|
|
barrier!();
|
|
let ret = f(&C { _marker: PhantomData });
|
|
barrier!();
|
|
basepri::write(old_basepri);
|
|
ret
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A type-level priority
|
|
pub struct P<T> {
|
|
_marker: PhantomData<T>,
|
|
}
|
|
|
|
impl<N> P<N> {
|
|
/// Turns this priority into a ceiling
|
|
pub fn as_ceiling(&self) -> &C<N> {
|
|
unsafe {
|
|
mem::transmute(self)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> P<T>
|
|
where
|
|
T: Unsigned,
|
|
{
|
|
#[doc(hidden)]
|
|
pub fn _hw() -> u8 {
|
|
logical2hw(T::to_u8())
|
|
}
|
|
}
|
|
|
|
/// Maps a `Resource` / `Peripheral` to its ceiling
|
|
///
|
|
/// Do not implement this trait yourself. This is an implementation detail.
|
|
pub unsafe trait ResourceLike {
|
|
/// The ceiling of the resource
|
|
type Ceiling;
|
|
}
|
|
|
|
unsafe impl<P, RC> ResourceLike for Peripheral<P, C<RC>> {
|
|
type Ceiling = RC;
|
|
}
|
|
|
|
unsafe impl<T, RC> ResourceLike for Resource<T, C<RC>> {
|
|
type Ceiling = RC;
|
|
}
|
|
|
|
/// Type-level `>=` operator
|
|
///
|
|
/// Do not implement this trait yourself. This is an implementation detail.
|
|
pub unsafe trait GreaterThanOrEqual<RHS> {}
|
|
|
|
/// Type-level `<=` operator
|
|
///
|
|
/// Do not implement this trait yourself. This is an implementation detail.
|
|
pub unsafe trait LessThanOrEqual<RHS> {}
|
|
|
|
/// A macro to assign ceilings to peripherals
|
|
///
|
|
/// **NOTE** A peripheral instance, like RCC, can only be bound to a *single*
|
|
/// ceiling. Trying to use this macro to bind the same peripheral to several
|
|
/// ceiling will result in a compiler error.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ``` ignore
|
|
/// #[macro_use]
|
|
/// extern crate cortex_m_rtfm;
|
|
/// // device crate generated using `svd2rust`
|
|
/// extern crate stm32f30x;
|
|
///
|
|
/// peripherals!(stm32f30x, {
|
|
/// GPIOA: Peripheral {
|
|
/// register_block: Gpioa,
|
|
/// ceiling: C1,
|
|
/// },
|
|
/// RCC: Peripheral {
|
|
/// register_block: Rcc,
|
|
/// ceiling: C0,
|
|
/// },
|
|
/// });
|
|
/// ```
|
|
#[macro_export]
|
|
macro_rules! peripherals {
|
|
($device:ident, {
|
|
$($PERIPHERAL:ident: Peripheral {
|
|
register_block: $RegisterBlock:ident,
|
|
ceiling: $C:ident,
|
|
},)+
|
|
}) => {
|
|
$(
|
|
#[allow(private_no_mangle_statics)]
|
|
#[no_mangle]
|
|
static $PERIPHERAL:
|
|
$crate::Peripheral<::$device::$RegisterBlock, $crate::$C> =
|
|
unsafe { $crate::Peripheral::_new(::$device::$PERIPHERAL) };
|
|
)+
|
|
}
|
|
}
|
|
|
|
/// A macro to declare tasks
|
|
///
|
|
/// **NOTE** This macro will expand to a `main` function.
|
|
///
|
|
/// Each `$task` is bound to an `$Interrupt` handler and has a priority `$P`.
|
|
/// The minimum priority of a task is `P1`. `$enabled` indicates whether the
|
|
/// task will be enabled before `idle` runs.
|
|
///
|
|
/// The `$Interrupt` handlers are defined in the `$device` crate.
|
|
///
|
|
/// Apart from defining the listed `$tasks`, the `init` and `idle` functions
|
|
/// must be defined as well. `init` has signature `fn(P0, &C16)`, and `idle` has
|
|
/// signature `fn(P0) -> !`.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ``` ignore
|
|
/// #[macro_use]
|
|
/// extern crate cortex_m_rtfm as rtfm;
|
|
/// // device crate generated using `svd2rust`
|
|
/// extern crate stm32f30x;
|
|
///
|
|
/// use rtfm::{C16, P0, P1, P2};
|
|
/// use stm32f30x::interrupt::{Exti0, Tim7};
|
|
///
|
|
/// tasks!(stm32f30x, {
|
|
/// periodic: Task {
|
|
/// interrupt: Tim7,
|
|
/// priority: P1,
|
|
/// enabled: true,
|
|
/// },
|
|
/// button: Task {
|
|
/// interrupt: Exti0,
|
|
/// priority: P2,
|
|
/// enabled: true,
|
|
/// },
|
|
/// });
|
|
///
|
|
/// fn init(priority: P0, ceiling: C16) {
|
|
/// // ..
|
|
/// }
|
|
///
|
|
/// fn idle(priority: P0) -> ! {
|
|
/// // Sleep
|
|
/// loop {
|
|
/// rtfm::wfi();
|
|
/// }
|
|
/// }
|
|
///
|
|
/// // NOTE signature must match the tasks! declaration
|
|
/// fn periodic(task: Tim7, priority: P1) {
|
|
/// // ..
|
|
/// }
|
|
///
|
|
/// fn button(task: Exti0, priority: P2) {
|
|
/// // ..
|
|
/// }
|
|
/// ```
|
|
#[macro_export]
|
|
macro_rules! tasks {
|
|
($device:ident, {
|
|
$($task:ident: Task {
|
|
interrupt:$Interrupt:ident,
|
|
priority: $P:ident,
|
|
enabled: $enabled:expr,
|
|
},)*
|
|
}) => {
|
|
fn main() {
|
|
$crate::atomic(|cmax| {
|
|
fn validate_signature(_: fn($crate::P0, &$crate::CMAX)) {}
|
|
|
|
validate_signature(init);
|
|
let p0 = unsafe { ::core::mem::transmute::<_, P0>(()) };
|
|
init(p0, cmax);
|
|
set_priorities();
|
|
enable_tasks();
|
|
});
|
|
|
|
fn validate_signature(_: fn($crate::P0) -> !) {}
|
|
|
|
validate_signature(idle);
|
|
let p0 = unsafe { ::core::mem::transmute::<_, P0>(()) };
|
|
idle(p0);
|
|
|
|
fn set_priorities() {
|
|
// NOTE(safe) this function runs in an interrupt free context
|
|
let _nvic = unsafe { &*$crate::_NVIC.get() };
|
|
|
|
$(
|
|
{
|
|
let hw = $crate::$P::_hw();
|
|
unsafe {
|
|
_nvic.set_priority(
|
|
::$device::interrupt::Interrupt::$Interrupt,
|
|
hw,
|
|
);
|
|
}
|
|
}
|
|
)*
|
|
|
|
// TODO freeze the NVIC.IPR register using the MPU, if available
|
|
}
|
|
|
|
fn enable_tasks() {
|
|
// NOTE(safe) this function runs in an interrupt free context
|
|
let _nvic = unsafe { &*$crate::_NVIC.get() };
|
|
|
|
$(
|
|
if $enabled {
|
|
$crate::enable(::$task);
|
|
}
|
|
)*
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[link_section = ".rodata.interrupts"]
|
|
#[used]
|
|
static INTERRUPTS: ::$device::interrupt::Handlers =
|
|
::$device::interrupt::Handlers {
|
|
$(
|
|
$Interrupt: {
|
|
extern "C" fn $task(
|
|
task: ::$device::interrupt::$Interrupt
|
|
) {
|
|
let p = unsafe {
|
|
::core::mem::transmute::<_, $crate::$P>(())
|
|
};
|
|
$crate::_validate_priority(&p);
|
|
::$task(task, p)
|
|
}
|
|
|
|
$task
|
|
},
|
|
)*
|
|
..::$device::interrupt::DEFAULT_HANDLERS
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
include!(concat!(env!("OUT_DIR"), "/prio.rs"));
|