From 032bb0f0e2f42ca2da9f149e0546c8c50ba7ef84 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Tue, 27 Jun 2017 20:08:02 -0500 Subject: [PATCH] rtfm! macro --- Cargo.toml | 23 +- build.rs | 126 ----- src/lib.rs | 1464 ++++++++++++++++++++-------------------------------- 3 files changed, 575 insertions(+), 1038 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e2cd37f31f..be567deb72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,24 +10,11 @@ keywords = ["arm", "cortex-m"] license = "MIT OR Apache-2.0" name = "cortex-m-rtfm" repository = "https://github.com/japaric/cortex-m-rtfm" -version = "0.1.1" - -[build-dependencies] -quote = "0.3.15" -syn = "0.11.10" +version = "0.2.0" [dependencies] -cortex-m = "0.2.6" -static-ref = "0.1.0" -typenum = "1.7.0" +static-ref = "0.2.0" -[dev-dependencies] -compiletest_rs = "0.2.5" - -[features] -# Number of priority bits -P2 = [] -P3 = [] -P4 = [] -P5 = [] -default = ["P4"] +[dependencies.cortex-m] +branch = "rfc" +git = "https://github.com/japaric/cortex-m" \ No newline at end of file diff --git a/build.rs b/build.rs index 19b1217020..4f70208493 100644 --- a/build.rs +++ b/build.rs @@ -1,13 +1,4 @@ -#[macro_use] -extern crate quote; -extern crate syn; - use std::env; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; - -use syn::{Ident, IntTy, Lit}; fn main() { let target = env::var("TARGET").unwrap(); @@ -16,122 +7,5 @@ fn main() { println!("cargo:rustc-cfg=thumbv6m"); } - let bits = if env::var_os("CARGO_FEATURE_P2").is_some() { - 2 - } else if env::var_os("CARGO_FEATURE_P3").is_some() { - 3 - } else if env::var_os("CARGO_FEATURE_P4").is_some() { - 4 - } else if env::var_os("CARGO_FEATURE_P5").is_some() { - 5 - } else { - panic!( - "Specify the number of priority bits through one of these Cargo \ - features: P2, P3, P4 or P5" - ); - }; - - let n = Lit::Int(bits, IntTy::Unsuffixed); - let mut tokens = vec![]; - tokens.push( - quote! { - const PRIORITY_BITS: u8 = #n; - }, - ); - - // Ceilings and thresholds - for i in 0..(1 << bits) + 1 { - let c = Ident::new(format!("C{}", i)); - let t = Ident::new(format!("T{}", i)); - let u = Ident::new(format!("U{}", i)); - - let doc = format!("A ceiling of {}", i); - tokens.push( - quote! { - #[doc = #doc] - pub type #c = ::typenum::#u; - }, - ); - - let doc = format!("A preemption threshold of {}", i); - tokens.push( - quote! { - #[doc = #doc] - pub type #t = Threshold<::typenum::#u>; - }, - ); - } - - // Priorities - for i in 0..(1 << bits) + 1 { - let p = Ident::new(format!("P{}", i)); - let u = Ident::new(format!("U{}", i)); - - let doc = format!( - "A priority of {}{}", - i, - if i == 0 { - ", the lowest priority" - } else if i == (1 << bits) { - ", the highest priority" - } else { - "" - } - ); - tokens.push( - quote! { - #[doc = #doc] - pub type #p = Priority<::typenum::#u>; - }, - ); - } - - // GreaterThanOrEqual & LessThanOrEqual - for i in 0..(1 << bits) + 1 { - for j in 0..(i + 1) { - let i = Ident::new(format!("U{}", i)); - let j = Ident::new(format!("U{}", j)); - - tokens.push( - quote! { - unsafe impl GreaterThanOrEqual<::typenum::#j> for - ::typenum::#i {} - - unsafe impl LessThanOrEqual<::typenum::#i> for - ::typenum::#j {} - }, - ); - } - } - - let u = Ident::new(format!("U{}", (1 << bits))); - let c = Ident::new(format!("C{}", (1 << bits))); - let p = Ident::new(format!("P{}", (1 << bits))); - let t = Ident::new(format!("T{}", (1 << bits))); - tokens.push( - quote! { - /// Maximum ceiling - pub type CMax = #c; - - /// Maximum priority - pub type PMax = #p; - - /// Maximum preemption threshold - pub type TMax = #t; - - /// Maximum priority level - pub type UMax = ::typenum::#u; - }, - ); - - let tokens = quote! { - #(#tokens)* - }; - - let out_dir = env::var("OUT_DIR").unwrap(); - let mut out = File::create(PathBuf::from(out_dir).join("prio.rs")).unwrap(); - - out.write_all(tokens.as_str().as_bytes()).unwrap(); - println!("cargo:rerun-if-changed=build.rs"); } diff --git a/src/lib.rs b/src/lib.rs index e325d27f08..f89f1025b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,973 +1,649 @@ -//! 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. -//! - Support for 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 a single call stack -//! and there's no hard dependency on a dynamic memory 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. -//! - 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::{P0, T0, TMax}; -//! -//! // TASKS (None in this example) -//! tasks!(stm32f30x, {}); -//! -//! // INITIALIZATION PHASE -//! fn init(_priority: P0, _threshold: &TMax) { -//! hprintln!("INIT"); -//! } -//! -//! // IDLE LOOP -//! fn idle(_priority: P0, _threshold: T0) -> ! { -//! 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`. -//! -//! Both `init` and `idle` have a priority of 0, the lowest priority. In RTFM, -//! a higher priority value means more urgent. -//! -//! # 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::{Local, P0, P1, T0, T1, TMax}; -//! -//! // INITIALIZATION PHASE -//! fn init(_priority: P0, _threshold: &TMax) { -//! // Configure TIM7 for periodic interrupts -//! // Configure GPIO for LED driving -//! } -//! -//! // IDLE LOOP -//! fn idle(_priority: P0, _threshold: T0) -> ! { -//! // Sleep -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! // TASKS -//! tasks!(stm32f30x, { -//! periodic: Task { -//! interrupt: Tim7, -//! priority: P1, -//! enabled: true, -//! }, -//! }); -//! -//! fn periodic(mut task: Tim7, _priority: P1, _threshold: T1) { -//! // Task local data -//! static STATE: Local = 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, P0, P1, Resource, T0, T1, TMax}; -//! -//! 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, C1> = Resource::new(Cell::new(0)); -//! -//! fn init(priority: P0, threshold: &TMax) { -//! // .. -//! } -//! -//! fn idle(priority: P0, threshold: T0) -> ! { -//! // Sleep -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! fn t1(_task: Tim6Dacunder, priority: P1, threshold: T1) { -//! let counter = COUNTER.access(&priority, &threshold); -//! -//! counter.set(counter.get() + 1); -//! } -//! -//! fn t2(_task: Tim7, priority: P1, threshold: T1) { -//! let counter = COUNTER.access(&priority, &threshold); -//! -//! 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::{C2, P0, P1, P2, Resource, T0, T1, T2, TMax}; -//! -//! tasks!(stm32f30x, { -//! t1: Task { -//! interrupt: Tim6Dacunder, -//! priority: P1, -//! enabled: true, -//! }, -//! t2: Task { -//! interrupt: Tim7, -//! priority: P2, -//! enabled: true, -//! }, -//! }); -//! -//! static COUNTER: Resource, C2> = Resource::new(Cell::new(0)); -//! -//! fn init(priority: P0, threshold: &TMax) { -//! // .. -//! } -//! -//! fn idle(priority: P0, threshold: T0) -> ! { -//! // Sleep -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! fn t1(_task: Tim6Dacunder, priority: P1, threshold: T1) { -//! // .. -//! -//! threshold.raise( -//! &COUNTER, |threshold: &T2| { -//! let counter = COUNTER.access(&priority, threshold); -//! -//! counter.set(counter.get() + 1); -//! } -//! ); -//! -//! // .. -//! } -//! -//! fn t2(_task: Tim7, priority: P2, threshold: T2) { -//! let counter = COUNTER.access(&priority, &threshold); -//! -//! 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 preemption -//! `threshold`. This creates a critical section, denoted by a closure; for -//! whose execution, `COUNTER` is accessible while `t2` is prevented 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 -//! preemption threshold 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 a priority of e.g. 3 (`P3`) effectively imposes a -//! threshold of 3 (`C3`) because only a 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::{P0, Peripheral, T0, TMax}; -//! -//! peripherals!(stm32f30x, { -//! GPIOA: Peripheral { -//! register_block: Gpioa, -//! ceiling: C0, -//! }, -//! RCC: Peripheral { -//! register_block: Rcc, -//! ceiling: C0, -//! }, -//! }); -//! -//! tasks!(stm32f30x, {}); -//! -//! fn init(priority: P0, threshold: &TMax) { -//! let gpioa = GPIOA.access(&priority, threshold); -//! let rcc = RCC.access(&priority, threshold); -//! -//! // .. -//! } -//! -//! fn idle(_priority: P0, _threshold: T0) -> ! { -//! // 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][srp]. -//! -//! [srp]: 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][rtfm] -//! -//! [rtfm]: http://www.diva-portal.org/smash/get/diva2:1005680/FULLTEXT01.pdf - -#![deny(missing_docs)] -#![deny(warnings)] #![feature(asm)] #![feature(const_fn)] -#![feature(optin_builtin_traits)] #![no_std] extern crate cortex_m; extern crate static_ref; -extern crate typenum; use core::cell::UnsafeCell; -use core::marker::PhantomData; -use core::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::interrupt::free as _free; pub use cortex_m::asm::{bkpt, wfi}; +pub use static_ref::Static; +#[cfg(not(thumbv6m))] +use cortex_m::register::{basepri_max, basepri}; -#[doc(hidden)] -pub use cortex_m::peripheral::NVIC as _NVIC; +pub const MAX_PRIORITY: u8 = 1 << PRIORITY_BITS; +const PRIORITY_BITS: u8 = 4; -/// Compiler barrier #[cfg(not(thumbv6m))] macro_rules! barrier { - () => { - asm!("" - : - : - : "memory" - : "volatile"); + () => { asm!("" ::: "memory" : "volatile") } +} + +unsafe fn claim( + value: T, + g: G, + f: F, + ceiling: u8, + threshold: &Threshold, +) -> R +where + G: FnOnce(T) -> U, + F: FnOnce(U, &Threshold) -> R, +{ + if ceiling > threshold.value { + match () { + #[cfg(thumbv6m)] + () => _free(|_| f(g(value), &Threshold::max())), + #[cfg(not(thumbv6m))] + () => { + if ceiling == MAX_PRIORITY { + _free(|_| f(g(value), &Threshold::max())) + } else { + let old = basepri::read(); + basepri_max::write(_logical2hw(ceiling)); + barrier!(); + let ret = f(g(value), &Threshold { value: ceiling }); + barrier!(); + basepri::write(old); + ret + } + } + } + } else { + f(g(value), threshold) } } -/// Task local data -/// -/// This data can only be accessed by the task `T` -pub struct Local { - _task: PhantomData, - data: UnsafeCell, +pub struct Peripheral

+where + P: 'static, +{ + ceiling: u8, + peripheral: cortex_m::peripheral::Peripheral

, } -impl Local { - /// Creates a task local variable with some initial `value` - pub const fn new(value: T) -> Self { - Local { - _task: PhantomData, - data: UnsafeCell::new(value), +unsafe impl

Sync for Peripheral

{} + +impl

Peripheral

{ + pub const unsafe fn new( + p: cortex_m::peripheral::Peripheral

, + ceiling: u8, + ) -> Self { + Peripheral { + peripheral: p, + ceiling: ceiling, } } - /// 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() } + pub unsafe fn claim(&'static self, threshold: &Threshold, f: F) -> R + where + F: FnOnce(&P, &Threshold) -> R, + { + claim( + &self.peripheral, + |data| &*data.get(), + f, + self.ceiling, + threshold, + ) } } -unsafe impl Sync for Local {} - -/// A resource with ceiling `C` -/// -/// A resource is used to share memory between two or more tasks -pub struct Resource { - _ceiling: PhantomData, +// FIXME ceiling should be a field of this struct but today that causes +// misoptimizations. The misoptimizations seem to be due to a rustc / LLVM bug +pub struct Resource { data: UnsafeCell, } -impl Resource -where - RC: GreaterThanOrEqual, - RC: LessThanOrEqual, -{ - /// Creates a new resource - pub const fn new(data: T) -> Self { - Resource { - _ceiling: PhantomData, - data: UnsafeCell::new(data), - } +impl Resource { + pub const fn new(value: T) -> Self { + Resource { data: UnsafeCell::new(value) } } -} -impl Resource { - /// Grants data race free and deadlock free access to the resource data + /// # Unsafety /// - /// 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 preemption threshold must be greater than or equal to the resource - /// ceiling - pub fn access<'cs, TP, PT>( + /// - Caller must ensure that this method is called from a task with + /// priority `P` where `Resource.ceiling <= P`. + pub unsafe fn claim( &'static self, - _task_priority: &Priority, - _preemption_threshold: &'cs Threshold, - ) -> Ref<'cs, T> + ceiling: u8, + threshold: &Threshold, + f: F, + ) -> R where - RC: GreaterThanOrEqual, - PT: GreaterThanOrEqual, + F: FnOnce(&Static, &Threshold) -> R, { - unsafe { Ref::new(&*self.data.get()) } + claim( + &self.data, + |data| Static::ref_(&*data.get()), + f, + ceiling, + threshold, + ) + } + + /// # Unsafety + /// + /// - Caller must ensure that this method is called from a task with + /// priority `P` where `Resource.ceiling <= P`. + /// + /// - Caller must take care to not break Rust borrowing rules + pub unsafe fn claim_mut( + &'static self, + ceiling: u8, + threshold: &Threshold, + f: F, + ) -> R + where + F: FnOnce(&mut Static, &Threshold) -> R, + { + claim( + &self.data, + |data| Static::ref_mut(&mut *data.get()), + f, + ceiling, + threshold, + ) + } + + pub unsafe fn get(&'static self) -> *mut T { + self.data.get() } } -unsafe impl Sync for Resource +unsafe impl Sync for Resource where T: Send, { } -/// A hardware peripheral as a resource -/// -/// To assign a ceiling to a peripheral, use the -/// [`peripherals!`](./macro.peripherals.html) macro -pub struct Peripheral -where - P: 'static, -{ - peripheral: cortex_m::peripheral::Peripheral

, - _ceiling: PhantomData, +pub struct Threshold { + value: u8, } -impl Peripheral -where - PC: GreaterThanOrEqual, - PC: LessThanOrEqual, -{ +impl Threshold { + pub unsafe fn max() -> Self { + Threshold { value: 1 << PRIORITY_BITS } + } + + pub unsafe fn new(value: u8) -> Self { + Threshold { value: value } + } +} + +/// Fault and system exceptions +#[allow(non_camel_case_types)] +#[doc(hidden)] +pub enum Exception { + /// Memory management. + MEN_MANAGE, + /// Pre-fetch fault, memory access fault. + BUS_FAULT, + /// Undefined instruction or illegal state. + USAGE_FAULT, + /// System service call via SWI instruction + SVCALL, + /// Pendable request for system service + PENDSV, + /// System tick timer + SYS_TICK, +} + +impl Exception { #[doc(hidden)] - pub const unsafe fn _new(peripheral: cortex_m::peripheral::Peripheral

,) - -> Self { - Peripheral { - _ceiling: PhantomData, - peripheral: peripheral, + pub fn nr(&self) -> usize { + match *self { + Exception::MEN_MANAGE => 4, + Exception::BUS_FAULT => 5, + Exception::USAGE_FAULT => 6, + Exception::SVCALL => 11, + Exception::PENDSV => 14, + Exception::SYS_TICK => 15, } } } -impl Peripheral { - /// See [Resource.access](./struct.Resource.html#method.access) - pub fn access<'cs, TP, PT>( - &'static self, - _task_priority: &Priority, - _preemption_threshold: &'cs Threshold, - ) -> Ref<'cs, Periph> - where - PC: GreaterThanOrEqual, - PT: GreaterThanOrEqual, - { - unsafe { Ref::new(&*self.peripheral.get()) } - } -} - -unsafe impl Sync for Peripheral {} - -/// Runs the closure `f` "atomically" -/// -/// No task can preempt the execution of the closure pub fn atomic(f: F) -> R where - F: FnOnce(&TMax) -> R, + F: FnOnce(&Threshold) -> R, { - let primask = ::cortex_m::register::primask::read(); - ::cortex_m::interrupt::disable(); - - let r = f(&Threshold { _marker: PhantomData }); - - // If the interrupts were active before our `disable` call, then re-enable - // them. Otherwise, keep them disabled - if primask.is_active() { - unsafe { ::cortex_m::interrupt::enable() } - } - - r + _free(|_| unsafe { f(&Threshold::max()) }) } -/// Disables a `task` -/// -/// The task won't run even if the underlying interrupt is raised -pub fn disable(_task: fn(T, Priority, Threshold)) -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) } +pub fn _logical2hw(i: u8) -> u8 { + ((1 << PRIORITY_BITS) - i) << (8 - PRIORITY_BITS) } -/// Enables a `task` -pub fn enable(_task: fn(T, Priority, Threshold)) -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(_task: fn(T, Priority, Threshold)) -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(_: &Priority) -where - TP: Cmp + LessThanOrEqual, -{ -} - -/// Preemption threshold -pub struct Threshold { - _marker: PhantomData, -} - -impl Threshold { - /// Raises the preemption threshold to match the `resource` ceiling - #[cfg(not(thumbv6m))] - pub fn raise(&self, _resource: &'static RES, f: F) -> R - where - RES: ResourceLike, - RC: Cmp + Cmp + Unsigned, - F: FnOnce(&Threshold) -> R, - { - unsafe { - let old_basepri = basepri::read(); - basepri_max::write(logical2hw(RC::to_u8())); - barrier!(); - let ret = f(&Threshold { _marker: PhantomData }); - barrier!(); - basepri::write(old_basepri); - ret - } - } -} - -impl !Send for Threshold {} - -/// Priority -pub struct Priority { - _marker: PhantomData, -} - -impl Priority -where - T: Unsigned, -{ - #[doc(hidden)] - pub fn _hw() -> u8 { - logical2hw(T::to_u8()) - } -} - -impl !Send for Priority {} - -/// 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 ResourceLike for Peripheral { - type Ceiling = PC; -} - -unsafe impl ResourceLike for Resource { - type Ceiling = RC; -} - -/// Type-level `>=` operator -/// -/// Do not implement this trait yourself. This is an implementation detail. -pub unsafe trait GreaterThanOrEqual {} - -/// Type-level `<=` operator -/// -/// Do not implement this trait yourself. This is an implementation detail. -pub unsafe trait LessThanOrEqual {} - -/// 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 -/// -/// NOTE With device crates generated using svd2rust 0.8+ you can omit the -/// register_block field. -/// -/// ``` 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) }; - )+ - }; - ($device:ident, { - $($PERIPHERAL:ident: Peripheral { - ceiling: $C:ident, - },)+ - }) => { - $( - #[allow(private_no_mangle_statics)] - #[no_mangle] - static $PERIPHERAL: - $crate::Peripheral<::$device::$PERIPHERAL, $crate::$C> = - unsafe { $crate::Peripheral::_new(::$device::$PERIPHERAL) }; - )+ - } -} +macro_rules! rtfm { + (device: $device:ident, -/// 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, &TMax)`, and `idle` -/// has signature `fn(P0) -> !`. -/// -/// # Example -/// -/// ``` ignore -/// #[feature(used)] -/// #[no_std] -/// -/// extern crate cortex_m_rt; -/// #[macro_use] -/// extern crate cortex_m_rtfm as rtfm; -/// // device crate generated using `svd2rust` -/// extern crate stm32f30x; -/// -/// use rtfm::{P0, P1, P2, T0, T1, T2, TMax}; -/// 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, threshold: &TMax) { -/// // .. -/// } -/// -/// fn idle(priority: P0, threshold: T0) -> ! { -/// // Sleep -/// loop { -/// rtfm::wfi(); -/// } -/// } -/// -/// // NOTE signature must match the tasks! declaration -/// fn periodic(task: Tim7, priority: P1, threshold: T1) { -/// // .. -/// } -/// -/// fn button(task: Exti0, priority: P2, threshold: T2) { -/// // .. -/// } -/// ``` -#[macro_export] -macro_rules! tasks { - ($device:ident, { - $($task:ident: Task { - interrupt:$Interrupt:ident, - priority: $P:ident, - enabled: $enabled:expr, - },)* - }) => { - fn main() { - $crate::atomic(|t_max| { - fn validate_signature(_: fn($crate::P0, &$crate::TMax)) {} + peripherals: { + $($pvar:ident @ $pceiling:expr,)* + }, - validate_signature(init); - let p0 = unsafe { ::core::mem::transmute::<_, P0>(()) }; - init(p0, t_max); - set_priorities(); - enable_tasks(); - }); + // FIXME ceiling should not be required; instead it should be derived from + // the other information. This is doable but causes misoptimizations. A fix + // requires better const support. + resources: { + $($rvar:ident: $rty:ty = $rval:expr; @ $rceiling:expr,)* + }, - fn validate_signature(_: fn($crate::P0, $crate::T0) -> !) {} + init: { + body: $init:path, + peripherals: [$($inperipheral:ident,)*], + }, - validate_signature(idle); - let p0 = unsafe { ::core::mem::transmute::<_, P0>(()) }; - let t0 = unsafe { ::core::mem::transmute::<_, T0>(()) }; - idle(p0, t0); + idle: { + body: $idle:path, + local: { + $($lvar:ident: $lty:ty = $lval:expr;)* + }, + peripherals: [$($idperipheral:ident,)*], + resources: [$($idresource:ident,)*], + }, - fn set_priorities() { - // NOTE(safe) this function runs in an interrupt free context - let _nvic = unsafe { &*$crate::_NVIC.get() }; + exceptions: { + $( + $EXCEPTION:ident: { + body: $ebody:path, + priority: $epriority:expr, + local: { + $($elvar:ident: $elty:ty = $elval:expr;)* + }, + peripherals: [$($eperipheral:ident,)*], + resources: [$($eresource:ident,)*], + }, + )* + }, + + interrupts: { + $( + $INTERRUPT:ident: { + body: $ibody:path, + priority: $ipriority:expr, + enabled: $enabled:expr, + local: { + $($ilvar:ident: $ilty:ty = $ilval:expr;)* + }, + peripherals: [$($iperipheral:ident,)*], + resources: [$($iresource:ident,)*], + }, + )* + }, + ) => { + mod _ceiling { + #![allow(non_upper_case_globals)] + #![allow(dead_code)] + + $( + pub const $rvar: u8 = $rceiling; + )* + + $( + pub const $pvar: u8 = $pceiling; + )* + + mod verify { + #![allow(dead_code)] + #![deny(const_err)] $( - { - let hw = $crate::$P::_hw(); - unsafe { - _nvic.set_priority( - ::$device::interrupt::Interrupt::$Interrupt, - hw, - ); - } - } + const $rvar: u8 = $crate::MAX_PRIORITY - $rceiling; )* - // TODO freeze the NVIC.IPR register using the MPU, if available + $( + const $pvar: u8 = $crate::MAX_PRIORITY - $pceiling; + )* + } + } + + pub mod _resource { + $( + #[allow(non_upper_case_globals)] + pub static $rvar: $crate::Resource<$rty> = + $crate::Resource::new($rval); + )* + + $( + #[allow(non_camel_case_types)] + pub struct $rvar { _0: () } + + impl $rvar { + pub unsafe fn new() -> Self { + $rvar { _0: () } + } + + pub fn claim( + &self, + t: &$crate::Threshold, + f: F, + ) -> R + where + F: FnOnce( + &$crate::Static<$rty>, + &$crate::Threshold, + ) -> R, + { + unsafe { + $rvar.claim(super::_ceiling::$rvar, t, f) + } + } + + pub fn claim_mut( + &mut self, + t: &$crate::Threshold, + f: F, + ) -> R + where + F: FnOnce( + &mut $crate::Static<$rty>, + &$crate::Threshold, + ) -> R, + { + unsafe { + $rvar.claim_mut(super::_ceiling::$rvar, t, f) + } + } + } + )* + } + + mod _peripheral { + // Peripherals + $( + static $pvar: + $crate::Peripheral<::$device::$pvar> = unsafe { + $crate::Peripheral::new( + ::$device::$pvar, + $pceiling, + ) + }; + + pub struct $pvar { _0: () } + + impl $pvar { + pub unsafe fn new() -> Self { + $pvar { _0: () } + } + + pub fn claim( + &self, + t: &$crate::Threshold, + f: F, + ) -> R + where + F: FnOnce( + &::$device::$pvar, + &$crate::Threshold, + ) -> R, + { + unsafe { + $pvar.claim(t, f) + } + } + } + )* + + } + + $( + #[allow(non_snake_case)] + mod $EXCEPTION { + pub struct Local { + $(pub $elvar: $crate::Static<$elty>,)* + } + + pub struct Resources { + $(pub $eresource: super::_resource::$eresource,)* + } + + pub struct Peripherals { + $(pub $eperipheral: super::_peripheral::$eperipheral,)* + } + } + )* + + $( + #[allow(non_snake_case)] + mod $INTERRUPT { + pub struct Local { + $(pub $ilvar: $crate::Static<$ilty>,)* + } + + pub struct Resources { + $(pub $iresource: super::_resource::$iresource,)* + } + + pub struct Peripherals { + $(pub $iperipheral: super::_peripheral::$iperipheral,)* + } + } + )* + + $( + #[allow(non_snake_case)] + #[no_mangle] + pub unsafe extern "C" fn $EXCEPTION() { + // verify task priority + #[allow(dead_code)] + #[deny(const_err)] + const $EXCEPTION: (u8, u8) = ( + $epriority - 1, + $crate::MAX_PRIORITY - $epriority, + ); + + // verify ceiling correctness + $( + #[allow(dead_code)] + #[allow(non_upper_case_globals)] + #[deny(const_err)] + const $eresource: u8 = _ceiling::$eresource - $epriority; + )* + + $( + #[allow(dead_code)] + #[allow(non_upper_case_globals)] + #[deny(const_err)] + const $eperipheral: u8 = + _ceiling::$eperipheral - $epriority; + )* + + // check that the interrupt handler exists + let _ = $crate::Exception::$EXCEPTION; + + // type check + // TODO Local should not appear in the signature if no local + // data is declared + // TODO Resources should on appear in the signature if no + // resources has been declared + let f: fn(&$crate::Threshold, + &mut $EXCEPTION::Local, + &$EXCEPTION::Peripherals, + &mut $EXCEPTION::Resources) = $ebody; + + static mut LOCAL: $EXCEPTION::Local = unsafe { + $EXCEPTION::Local { + $($elvar: $crate::Static::new($elval),)* + } + }; + + f( + &$crate::Threshold::new($epriority), + &mut LOCAL, + &$EXCEPTION::Peripherals { + $($eperipheral: _peripheral::$eperipheral::new(),)* + }, + &mut $EXCEPTION::Resources { + $($eresource: _resource::$eresource::new(),)* + }, + ) + } + )* + + $( + #[allow(non_snake_case)] + #[no_mangle] + pub unsafe extern "C" fn $INTERRUPT() { + // verify task priority + #[allow(dead_code)] + #[deny(const_err)] + const $INTERRUPT: (u8, u8) = ( + $ipriority - 1, + $crate::MAX_PRIORITY - $ipriority, + ); + + // verify ceiling correctness + $( + #[allow(dead_code)] + #[allow(non_upper_case_globals)] + #[deny(const_err)] + const $iresource: u8 = _ceiling::$iresource - $ipriority; + )* + + // check that the interrupt handler exists + let _ = $device::interrupt::Interrupt::$INTERRUPT; + + // type check + // TODO Local should not appear in the signature if no local + // data is declared + // TODO Resources should on appear in the signature if no + // resources has been declared + let f: fn(&$crate::Threshold, + &mut $INTERRUPT::Local, + &$INTERRUPT::Peripherals, + &mut $INTERRUPT::Resources) = $ibody; + + #[allow(unused_unsafe)] + static mut LOCAL: $INTERRUPT::Local = unsafe { + $INTERRUPT::Local { + $($ilvar: $crate::Static::new($ilval)),* + } + }; + + f( + &$crate::Threshold::new($ipriority), + &mut LOCAL, + &$INTERRUPT::Peripherals { + $($iperipheral: _peripheral::$iperipheral::new(),)* + }, + &mut $INTERRUPT::Resources { + $($iresource: _resource::$iresource::new(),)* + }, + ) + } + )* + + #[allow(non_snake_case)] + mod init { + pub struct Peripherals<'a> { + pub _0: ::core::marker::PhantomData<&'a ()>, + $(pub $inperipheral: &'a ::$device::$inperipheral,)* } - fn enable_tasks() { - // NOTE(safe) this function runs in an interrupt free context - let _nvic = unsafe { &*$crate::_NVIC.get() }; + pub struct Resources<'a> { + pub _0: ::core::marker::PhantomData<&'a ()>, + $(pub $rvar: &'a mut $crate::Static<$rty>,)* + } + } + + #[allow(non_snake_case)] + mod idle { + pub struct Local { + $(pub $lvar: $crate::Static<$lty>,)* + } + + pub struct Peripherals { + $(pub $idperipheral: super::_peripheral::$idperipheral,)* + } + + pub struct Resources { + $(pub $idresource: super::_resource::$idresource,)* + } + } + + fn main() { + // type check + let init: fn( + init::Peripherals, + init::Resources, + ) = $init; + // TODO Local should not appear in the signature if no local data is + // declared + // TODO Resources should on appear in the signature if no resources + // has been declared + let idle: fn(&$crate::Threshold, + &'static mut idle::Local, + &idle::Peripherals, + &mut idle::Resources) -> ! = $idle; + + $crate::_free(|cs| unsafe { + init( + init::Peripherals { + _0: ::core::marker::PhantomData, + $( + $inperipheral: $device::$inperipheral.borrow(cs), + )* + }, + init::Resources { + _0: ::core::marker::PhantomData, + $( + $rvar: $crate::Static::ref_mut( + &mut *_resource::$rvar.get(), + ), + )* + }, + ); + + let _scb = $device::SCB.borrow(cs); + + $( + _scb.shpr[$crate::Exception::$EXCEPTION.nr() - 4].write( + $crate::_logical2hw($epriority), + ); + )* + + let _nvic = $device::NVIC.borrow(cs); + + $( + _nvic.set_priority( + $device::interrupt::Interrupt::$INTERRUPT, + $crate::_logical2hw($ipriority), + ); + )* $( if $enabled { - $crate::enable(::$task); + _nvic.enable($device::interrupt::Interrupt::$INTERRUPT); } )* - } + }); - #[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 - ) { - fn validate_signature( - _: fn(::$device::interrupt::$Interrupt, - $crate::Priority, - $crate::Threshold)) {} - validate_signature(::$task); - let p = unsafe { - ::core::mem::transmute::<_, $crate::$P>(()) - }; - let t = unsafe { - ::core::mem::transmute(()) - }; - $crate::_validate_priority(&p); - ::$task(task, p, t) - } + #[allow(unused_unsafe)] + static mut LOCAL: idle::Local = unsafe { + idle::Local { + $($lvar: $crate::Static::new($lval)),* + } + }; - $task + unsafe { + idle( + &$crate::Threshold::new(0), + &mut LOCAL, + &idle::Peripherals { + $( + $idperipheral: _peripheral::$idperipheral::new(), + )* }, - )* - ..::$device::interrupt::DEFAULT_HANDLERS - }; + &mut idle::Resources { + $( + $idresource: _resource::$idresource::new(), + )* + }, + ) + } } } } - -include!(concat!(env!("OUT_DIR"), "/prio.rs"));