diff --git a/Cargo.toml b/Cargo.toml index fc196a20ba..977a517a63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,15 +15,15 @@ version = "0.3.2" [dependencies] cortex-m = "0.4.0" cortex-m-rtfm-macros = { path = "macros", version = "0.3.1" } -heapless = "0.2.7" -rtfm-core = "0.2.0" -untagged-option = "0.1.1" +heapless = "0.3.4" +typenum = "1.10.0" [target.'cfg(target_arch = "x86_64")'.dev-dependencies] compiletest_rs = "0.3.5" [dev-dependencies] panic-abort = "0.1.1" +panic-itm = "0.1.0" [dev-dependencies.stm32f103xx] features = ["rt"] @@ -33,4 +33,4 @@ version = "0.9.0" cm7-r0p1 = ["cortex-m/cm7-r0p1"] [profile.release] -lto = true +lto = true \ No newline at end of file diff --git a/build.rs b/build.rs index ebfee06ce3..96d4c2169e 100644 --- a/build.rs +++ b/build.rs @@ -5,6 +5,14 @@ fn main() { if target.starts_with("thumbv6m-") { println!("cargo:rustc-cfg=armv6m"); + } else if target.starts_with("thumbv7m-") { + println!("cargo:rustc-cfg=armv7m"); + } else if target.starts_with("thumbv7em-") { + println!("cargo:rustc-cfg=armv7m"); + } + + if target.ends_with("-eabihf") { + println!("cargo:rustc-cfg=has_fpu"); } println!("cargo:rerun-if-changed=build.rs"); diff --git a/examples/async-after.rs b/examples/async-after.rs new file mode 100644 index 0000000000..1c76ca2099 --- /dev/null +++ b/examples/async-after.rs @@ -0,0 +1,55 @@ +#![allow(warnings)] +// #![deny(unsafe_code)] +// #![deny(warnings)] +#![feature(proc_macro)] +#![no_std] + +#[macro_use] +extern crate cortex_m; +extern crate cortex_m_rtfm as rtfm; +// extern crate panic_abort; +extern crate panic_itm; +extern crate stm32f103xx; + +use core::mem; + +use cortex_m::asm; +use cortex_m::peripheral::{DWT, ITM}; +use rtfm::{app, Resource}; + +app! { + device: stm32f103xx, + + free_interrupts: [EXTI1], + + tasks: { + exti0: { + interrupt: EXTI0, + async_after: [a], + }, + + a: {}, + }, +} + +const S: u32 = 8_000_000; + +#[inline(always)] +fn init(mut ctxt: init::Context) -> init::LateResources { + init::LateResources {} +} + +#[inline(always)] +fn idle(ctxt: idle::Context) -> ! { + loop { + asm::wfi(); + } +} + +fn exti0(mut ctxt: exti0::Context) { + ctxt.async.a.post(&mut ctxt.threshold, 1 * S, ()); +} + +fn a(ctxt: a::Context) { + asm::bkpt(); +} diff --git a/examples/async.rs b/examples/async.rs new file mode 100644 index 0000000000..f0dae49307 --- /dev/null +++ b/examples/async.rs @@ -0,0 +1,57 @@ +#![allow(warnings)] +// #![deny(unsafe_code)] +// #![deny(warnings)] +#![feature(proc_macro)] +#![no_std] + +#[macro_use] +extern crate cortex_m; +extern crate cortex_m_rtfm as rtfm; +// extern crate panic_abort; +extern crate panic_itm; +extern crate stm32f103xx; + +use core::mem; + +use cortex_m::asm; +use cortex_m::peripheral::{DWT, ITM}; +use rtfm::{app, Resource}; + +app! { + device: stm32f103xx, + + init: { + async: [a], + }, + + free_interrupts: [EXTI1], + + tasks: { + exti0: { + interrupt: EXTI0, + async: [a], + }, + + a: {}, + }, +} + +#[inline(always)] +fn init(mut ctxt: init::Context) -> init::LateResources { + init::LateResources {} +} + +#[inline(always)] +fn idle(ctxt: idle::Context) -> ! { + loop { + asm::wfi(); + } +} + +fn exti0(mut ctxt: exti0::Context) { + ctxt.async.a.post(&mut ctxt.threshold, ()); +} + +fn a(ctxt: a::Context) { + asm::bkpt(); +} diff --git a/examples/custom-type.rs b/examples/custom-type.rs deleted file mode 100644 index 79d6cc461c..0000000000 --- a/examples/custom-type.rs +++ /dev/null @@ -1,50 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![feature(proc_macro)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Threshold}; - -pub struct Foo; - -app! { - device: stm32f103xx, - - resources: { - static CO_OWNED: Foo = Foo; - static ON: Foo = Foo; - static OWNED: Foo = Foo; - static SHARED: Foo = Foo; - }, - - idle: { - resources: [OWNED, SHARED], - }, - - tasks: { - SYS_TICK: { - path: sys_tick, - resources: [CO_OWNED, ON, SHARED], - }, - - TIM2: { - enabled: false, - path: tim2, - priority: 1, - resources: [CO_OWNED], - }, - }, -} - -fn init(_p: ::init::Peripherals, _r: ::init::Resources) {} - -fn idle(_t: &mut Threshold, _r: ::idle::Resources) -> ! { - loop {} -} - -fn sys_tick(_t: &mut Threshold, _r: SYS_TICK::Resources) {} - -fn tim2(_t: &mut Threshold, _r: TIM2::Resources) {} diff --git a/examples/empty.rs b/examples/empty.rs new file mode 100644 index 0000000000..e4f2f301b3 --- /dev/null +++ b/examples/empty.rs @@ -0,0 +1,33 @@ +#![allow(warnings)] +// #![deny(unsafe_code)] +// #![deny(warnings)] +#![feature(proc_macro)] +#![no_std] + +#[macro_use] +extern crate cortex_m; +extern crate cortex_m_rtfm as rtfm; +// extern crate panic_abort; +extern crate panic_itm; +extern crate stm32f103xx; + +use core::mem; + +use cortex_m::asm; +use rtfm::{app, Resource}; + +app! { + device: stm32f103xx, +} + +#[inline(always)] +fn init(mut ctxt: init::Context) -> init::LateResources { + init::LateResources {} +} + +#[inline(always)] +fn idle(ctxt: idle::Context) -> ! { + loop { + asm::wfi(); + } +} diff --git a/examples/full-syntax.rs b/examples/full-syntax.rs deleted file mode 100644 index 5b27412265..0000000000 --- a/examples/full-syntax.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! A showcase of the `app!` macro syntax -#![deny(unsafe_code)] -#![deny(warnings)] -#![feature(proc_macro)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Threshold}; - -app! { - device: stm32f103xx, - - resources: { - static CO_OWNED: u32 = 0; - static ON: bool = false; - static OWNED: bool = false; - static SHARED: bool = false; - }, - - init: { - // This is the path to the `init` function - // - // `init` doesn't necessarily has to be in the root of the crate - path: main::init, - }, - - idle: { - // This is a path to the `idle` function - // - // `idle` doesn't necessarily has to be in the root of the crate - path: main::idle, - resources: [OWNED, SHARED], - }, - - tasks: { - SYS_TICK: { - path: sys_tick, - // If omitted priority is assumed to be 1 - // priority: 1, - resources: [CO_OWNED, ON, SHARED], - }, - - TIM2: { - // Tasks are enabled, between `init` and `idle`, by default but they - // can start disabled if `false` is specified here - enabled: false, - path: tim2, - priority: 1, - resources: [CO_OWNED], - }, - }, -} - -mod main { - use rtfm::{self, Resource, Threshold}; - - pub fn init(_p: ::init::Peripherals, _r: ::init::Resources) {} - - pub fn idle(t: &mut Threshold, mut r: ::idle::Resources) -> ! { - loop { - *r.OWNED != *r.OWNED; - - if *r.OWNED { - if r.SHARED.claim(t, |shared, _| *shared) { - rtfm::wfi(); - } - } else { - r.SHARED.claim_mut(t, |shared, _| *shared = !*shared); - } - } - } -} - -fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { - *r.ON = !*r.ON; - - *r.CO_OWNED += 1; -} - -fn tim2(_t: &mut Threshold, mut r: TIM2::Resources) { - *r.CO_OWNED += 1; -} diff --git a/examples/generics.rs b/examples/generics.rs deleted file mode 100644 index ca7726d0b7..0000000000 --- a/examples/generics.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Working with resources in a generic fashion -#![deny(unsafe_code)] -#![deny(warnings)] -#![feature(proc_macro)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Resource, Threshold}; -use stm32f103xx::{SPI1, GPIOA}; - -app! { - device: stm32f103xx, - - resources: { - static GPIOA: GPIOA; - static SPI1: SPI1; - }, - - tasks: { - EXTI0: { - path: exti0, - priority: 1, - resources: [GPIOA, SPI1], - }, - - EXTI1: { - path: exti1, - priority: 2, - resources: [GPIOA, SPI1], - }, - }, -} - -fn init(p: init::Peripherals) -> init::LateResources { - init::LateResources { - GPIOA: p.device.GPIOA, - SPI1: p.device.SPI1, - } -} - -fn idle() -> ! { - loop { - rtfm::wfi(); - } -} - -// A generic function that uses some resources -fn work(t: &mut Threshold, gpioa: &G, spi1: &S) -where - G: Resource, - S: Resource, -{ - gpioa.claim(t, |_gpioa, t| { - // drive NSS low - - spi1.claim(t, |_spi1, _| { - // transfer data - }); - - // drive NSS high - }); -} - -// This task needs critical sections to access the resources -fn exti0(t: &mut Threshold, r: EXTI0::Resources) { - work(t, &r.GPIOA, &r.SPI1); -} - -// This task has direct access to the resources -fn exti1(t: &mut Threshold, r: EXTI1::Resources) { - work(t, &r.GPIOA, &r.SPI1); -} diff --git a/examples/interrupt.rs b/examples/interrupt.rs new file mode 100644 index 0000000000..a5cead41d5 --- /dev/null +++ b/examples/interrupt.rs @@ -0,0 +1,42 @@ +#![allow(warnings)] +// #![deny(unsafe_code)] +// #![deny(warnings)] +#![feature(proc_macro)] +#![no_std] + +#[macro_use] +extern crate cortex_m; +extern crate cortex_m_rtfm as rtfm; +// extern crate panic_abort; +extern crate panic_itm; +extern crate stm32f103xx; + +use core::mem; + +use cortex_m::asm; +use cortex_m::peripheral::{DWT, ITM}; +use rtfm::{app, Resource}; + +app! { + device: stm32f103xx, + + tasks: { + exti0: { + interrupt: EXTI0, + }, + }, +} + +#[inline(always)] +fn init(mut ctxt: init::Context) -> init::LateResources { + init::LateResources {} +} + +#[inline(always)] +fn idle(ctxt: idle::Context) -> ! { + loop { + asm::wfi(); + } +} + +fn exti0(ctxt: exti0::Context) {} diff --git a/examples/late-resources.rs b/examples/late-resources.rs deleted file mode 100644 index 07c321f673..0000000000 --- a/examples/late-resources.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! Demonstrates initialization of resources in `init`. -#![deny(unsafe_code)] -#![deny(warnings)] -#![feature(proc_macro)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Threshold}; - -app! { - device: stm32f103xx, - - resources: { - // Usually, resources are initialized with a constant initializer: - static ON: bool = false; - - // However, there are cases where this is not possible or not desired. - // For example, there may not be a sensible value to use, or the type may - // not be constructible in a constant (like `Vec`). - // - // While it is possible to use an `Option` in some cases, that requires - // you to properly initialize it and `.unwrap()` it at every use. It - // also consumes more memory. - // - // To solve this, it is possible to defer initialization of resources to - // `init` by omitting the initializer. Doing that will require `init` to - // return the values of all "late" resources. - static IP_ADDRESS: u32; - - // PORT is used by 2 tasks, making it a shared resource. This just tests - // another internal code path and is not important for the example. - static PORT: u16; - }, - - idle: { - // Test that late resources can be used in idle - resources: [IP_ADDRESS], - }, - - tasks: { - SYS_TICK: { - priority: 1, - path: sys_tick, - resources: [IP_ADDRESS, PORT, ON], - }, - - EXTI0: { - priority: 2, - path: exti0, - resources: [PORT], - } - } -} - -// The signature of `init` is now required to have a specific return type. -fn init(_p: init::Peripherals, _r: init::Resources) -> init::LateResources { - // `init::Resources` does not contain `IP_ADDRESS`, since it is not yet - // initialized. - //_r.IP_ADDRESS; // doesn't compile - - // ...obtain value for IP_ADDRESS from EEPROM/DHCP... - let ip_address = 0x7f000001; - - init::LateResources { - // This struct will contain fields for all resources with omitted - // initializers. - IP_ADDRESS: ip_address, - PORT: 0, - } -} - -fn sys_tick(_t: &mut Threshold, r: SYS_TICK::Resources) { - // Other tasks can access late resources like any other, since they are - // guaranteed to be initialized when tasks are run. - - r.IP_ADDRESS; -} - -fn exti0(_t: &mut Threshold, _r: EXTI0::Resources) {} - -fn idle(_t: &mut Threshold, _r: idle::Resources) -> ! { - loop { - rtfm::wfi(); - } -} diff --git a/examples/nested.rs b/examples/nested.rs deleted file mode 100644 index 6af70879d4..0000000000 --- a/examples/nested.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! Nesting claims and how the preemption threshold works -//! -//! If you run this program you'll hit the breakpoints as indicated by the -//! letters in the comments: A, then B, then C, etc. -#![deny(unsafe_code)] -#![deny(warnings)] -#![feature(proc_macro)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Resource, Threshold}; -use stm32f103xx::Interrupt; - -app! { - device: stm32f103xx, - - resources: { - static LOW: u64 = 0; - static HIGH: u64 = 0; - }, - - tasks: { - EXTI0: { - path: exti0, - priority: 1, - resources: [LOW, HIGH], - }, - - EXTI1: { - path: exti1, - priority: 2, - resources: [LOW], - }, - - EXTI2: { - path: exti2, - priority: 3, - resources: [HIGH], - }, - }, -} - -fn init(_p: init::Peripherals, _r: init::Resources) {} - -fn idle() -> ! { - // A - rtfm::bkpt(); - - // Sets task `exti0` as pending - // - // Because `exti0` has higher priority than `idle` it will be executed - // immediately - rtfm::set_pending(Interrupt::EXTI0); // ~> exti0 - - loop { - rtfm::wfi(); - } -} - -#[allow(non_snake_case)] -fn exti0(t: &mut Threshold, EXTI0::Resources { mut LOW, mut HIGH }: EXTI0::Resources) { - // Because this task has a priority of 1 the preemption threshold `t` also - // starts at 1 - - // B - rtfm::bkpt(); - - // Because `exti1` has higher priority than `exti0` it can preempt it - rtfm::set_pending(Interrupt::EXTI1); // ~> exti1 - - // A claim creates a critical section - LOW.claim_mut(t, |_low, t| { - // This claim increases the preemption threshold to 2 - // - // 2 is just high enough to not race with task `exti1` for access to the - // `LOW` resource - - // D - rtfm::bkpt(); - - // Now `exti1` can't preempt this task because its priority is equal to - // the current preemption threshold - rtfm::set_pending(Interrupt::EXTI1); - - // But `exti2` can, because its priority is higher than the current - // preemption threshold - rtfm::set_pending(Interrupt::EXTI2); // ~> exti2 - - // F - rtfm::bkpt(); - - // Claims can be nested - HIGH.claim_mut(t, |_high, _| { - // This claim increases the preemption threshold to 3 - - // Now `exti2` can't preempt this task - rtfm::set_pending(Interrupt::EXTI2); - - // G - rtfm::bkpt(); - }); - - // Upon leaving the critical section the preemption threshold drops back - // to 2 and `exti2` immediately preempts this task - // ~> exti2 - }); - - // Once again the preemption threshold drops but this time to 1. Now the - // pending `exti1` task can preempt this task - // ~> exti1 -} - -fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) { - // C, I - rtfm::bkpt(); -} - -fn exti2(_t: &mut Threshold, _r: EXTI2::Resources) { - // E, H - rtfm::bkpt(); -} diff --git a/examples/one-task.rs b/examples/one-task.rs deleted file mode 100644 index c62fbbfed8..0000000000 --- a/examples/one-task.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! An application with one task -#![deny(unsafe_code)] -#![deny(warnings)] -#![feature(proc_macro)] -#![no_std] - -extern crate cortex_m; -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use cortex_m::peripheral::syst::SystClkSource; -use rtfm::{app, Threshold}; -use stm32f103xx::GPIOC; - -app! { - device: stm32f103xx, - - // Here data resources are declared - // - // Data resources are static variables that are safe to share across tasks - resources: { - // Declaration of resources looks exactly like declaration of static - // variables - static ON: bool = false; - }, - - // Here tasks are declared - // - // Each task corresponds to an interrupt or an exception. Every time the - // interrupt or exception becomes *pending* the corresponding task handler - // will be executed. - tasks: { - // Here we declare that we'll use the SYS_TICK exception as a task - SYS_TICK: { - // Path to the task handler - path: sys_tick, - - // These are the resources this task has access to. - // - // The resources listed here must also appear in `app.resources` - resources: [ON], - }, - } -} - -fn init(mut p: init::Peripherals, r: init::Resources) { - // `init` can modify all the `resources` declared in `app!` - r.ON; - - // power on GPIOC - p.device.RCC.apb2enr.modify(|_, w| w.iopcen().enabled()); - - // configure PC13 as output - p.device.GPIOC.bsrr.write(|w| w.bs13().set()); - p.device - .GPIOC - .crh - .modify(|_, w| w.mode13().output().cnf13().push()); - - // configure the system timer to generate one interrupt every second - p.core.SYST.set_clock_source(SystClkSource::Core); - p.core.SYST.set_reload(8_000_000); // 1s - p.core.SYST.enable_interrupt(); - p.core.SYST.enable_counter(); -} - -fn idle() -> ! { - loop { - rtfm::wfi(); - } -} - -// This is the task handler of the SYS_TICK exception -// -// `_t` is the preemption threshold token. We won't use it in this program. -// -// `r` is the set of resources this task has access to. `SYS_TICK::Resources` -// has one field per resource declared in `app!`. -#[allow(unsafe_code)] -fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { - // toggle state - *r.ON = !*r.ON; - - if *r.ON { - // set the pin PC13 high - // NOTE(unsafe) atomic write to a stateless register - unsafe { - (*GPIOC::ptr()).bsrr.write(|w| w.bs13().set()); - } - } else { - // set the pin PC13 low - // NOTE(unsafe) atomic write to a stateless register - unsafe { - (*GPIOC::ptr()).bsrr.write(|w| w.br13().reset()); - } - } -} diff --git a/examples/periodic-payload.rs b/examples/periodic-payload.rs new file mode 100644 index 0000000000..866a256bce --- /dev/null +++ b/examples/periodic-payload.rs @@ -0,0 +1,91 @@ +// # -Os +// init +// a(bl=8000000, now=8000180, input=0) +// a(bl=16000000, now=16000180, input=1) +// a(bl=24000000, now=24000180, input=2) +// +// # -O3 +// init +// a(bl=8000000, now=8000168, input=0) +// a(bl=16000000, now=16000168, input=1) +// a(bl=24000000, now=24000168, input=2) + +#![allow(warnings)] +// #![deny(unsafe_code)] +// #![deny(warnings)] +#![feature(proc_macro)] +#![no_std] + +#[macro_use] +extern crate cortex_m; +extern crate cortex_m_rtfm as rtfm; +// extern crate panic_abort; +extern crate panic_itm; +extern crate stm32f103xx; + +use core::mem; + +use cortex_m::asm; +use cortex_m::peripheral::{DWT, ITM}; +use rtfm::{app, Resource}; + +app! { + device: stm32f103xx, + + resources: { + static ITM: ITM; + }, + + init: { + async_after: [a], + }, + + free_interrupts: [EXTI0], + + tasks: { + a: { + async_after: [a], + input: u16, + resources: [ITM], + }, + }, +} + +const MS: u32 = 8_000; +const S: u32 = 1_000 * MS; + +#[inline(always)] +fn init(mut ctxt: init::Context) -> init::LateResources { + iprintln!(&mut ctxt.core.ITM.stim[0], "init"); + + ctxt.async.a.post(&mut ctxt.threshold, 1 * S, 0).ok(); + + init::LateResources { ITM: ctxt.core.ITM } +} + +#[inline(always)] +fn idle(ctxt: idle::Context) -> ! { + loop { + asm::wfi(); + } +} + +fn a(mut ctxt: a::Context) { + let now = DWT::get_cycle_count(); + let input = ctxt.input; + + let bl = ctxt.baseline; + let itm = ctxt.resources.ITM; + iprintln!( + &mut itm.stim[0], + "a(bl={}, now={}, input={})", + bl, + now, + input + ); + + ctxt.async + .a + .post(&mut ctxt.threshold, 1 * S, input + 1) + .ok(); +} diff --git a/examples/periodic-preemption-payload.rs b/examples/periodic-preemption-payload.rs new file mode 100644 index 0000000000..ead579ccb4 --- /dev/null +++ b/examples/periodic-preemption-payload.rs @@ -0,0 +1,130 @@ +// # -Os +// a(bl=16000000, now=16000248, input=0) +// b(bl=24000000, now=24000251, input=0) +// a(bl=32000000, now=32000248, input=1) +// b(bl=48000000, now=48000283, input=1) +// a(bl=48000000, now=48002427, input=2) +// a(bl=64000000, now=64000248, input=3) +// b(bl=72000000, now=72000251, input=2) +// a(bl=80000000, now=80000248, input=4) +// b(bl=96000000, now=96000283, input=3) +// a(bl=96000000, now=96002427, input=5) + +// # -O3 +// init +// a(bl=16000000, now=16000231, input=0) +// b(bl=24000000, now=24000230, input=0) +// a(bl=32000000, now=32000231, input=1) +// b(bl=48000000, now=48000259, input=1) +// a(bl=48000000, now=48002397, input=2) +// a(bl=64000000, now=64000231, input=3) +// b(bl=72000000, now=72000230, input=2) +// a(bl=80000000, now=80000231, input=4) +// b(bl=96000000, now=96000259, input=3) +// a(bl=96000000, now=96002397, input=5) + +#![allow(warnings)] +// #![deny(unsafe_code)] +// #![deny(warnings)] +#![feature(proc_macro)] +#![no_std] + +#[macro_use] +extern crate cortex_m; +extern crate cortex_m_rtfm as rtfm; +// extern crate panic_abort; +extern crate panic_itm; +extern crate stm32f103xx; + +use core::mem; + +use cortex_m::asm; +use cortex_m::peripheral::{DWT, ITM}; +use rtfm::{app, Resource}; + +app! { + device: stm32f103xx, + + resources: { + static ITM: ITM; + }, + + init: { + async_after: [a, b], + }, + + free_interrupts: [EXTI0, EXTI1], + + tasks: { + a: { + async_after: [a], + input: u32, + resources: [ITM], + }, + + b: { + async_after: [b], + input: u32, + priority: 2, + resources: [ITM], + }, + }, +} + +const MS: u32 = 8_000; +const S: u32 = 1_000 * MS; + +#[inline(always)] +fn init(mut ctxt: init::Context) -> init::LateResources { + iprintln!(&mut ctxt.core.ITM.stim[0], "init"); + + ctxt.async.a.post(&mut ctxt.threshold, 2 * S, 0).ok(); + ctxt.async.b.post(&mut ctxt.threshold, 3 * S, 0).ok(); + + init::LateResources { ITM: ctxt.core.ITM } +} + +#[inline(always)] +fn idle(ctxt: idle::Context) -> ! { + loop { + asm::wfi(); + } +} + +fn a(mut ctxt: a::Context) { + let now = DWT::get_cycle_count(); + + let input = ctxt.input; + let bl = ctxt.baseline; + ctxt.resources.ITM.claim_mut(&mut ctxt.threshold, |itm, _| { + iprintln!( + &mut itm.stim[0], + "a(bl={}, now={}, input={})", + bl, + now, + input + ); + }); + + ctxt.async + .a + .post(&mut ctxt.threshold, 2 * S, input + 1) + .ok(); +} + +fn b(mut ctxt: b::Context) { + let now = DWT::get_cycle_count(); + + let bl = ctxt.baseline; + let input = ctxt.input; + let t = &mut ctxt.threshold; + iprintln!( + &mut ctxt.resources.ITM.borrow_mut(t).stim[0], + "b(bl={}, now={}, input={})", + bl, + now, + input, + ); + + ctxt.async.b.post(t, 3 * S, input + 1).ok(); +} diff --git a/examples/periodic-preemption.rs b/examples/periodic-preemption.rs new file mode 100644 index 0000000000..95b14c8d5d --- /dev/null +++ b/examples/periodic-preemption.rs @@ -0,0 +1,117 @@ +// # -Os +// init +// a(bl=16000000, now=16000249) +// b(bl=24000000, now=24000248) +// a(bl=32000000, now=32000249) +// b(bl=48000000, now=48000282) +// a(bl=48000000, now=48001731) +// a(bl=64000000, now=64000249) +// b(bl=72000000, now=72000248) +// a(bl=80000000, now=80000249) +// b(bl=96000000, now=96000282) +// a(bl=96000000, now=96001731) + +// # -O3 +// init +// a(bl=16000000, now=16000228) +// b(bl=24000000, now=24000231) +// a(bl=32000000, now=32000228) +// b(bl=48000000, now=48000257) +// a(bl=48000000, now=48001705) +// a(bl=64000000, now=64000228) +// b(bl=72000000, now=72000231) +// a(bl=80000000, now=80000228) +// b(bl=96000000, now=96000257) +// a(bl=96000000, now=96001705) + +#![allow(warnings)] +// #![deny(unsafe_code)] +// #![deny(warnings)] +#![feature(proc_macro)] +#![no_std] + +#[macro_use] +extern crate cortex_m; +extern crate cortex_m_rtfm as rtfm; +// extern crate panic_abort; +extern crate panic_itm; +extern crate stm32f103xx; + +use core::mem; + +use cortex_m::asm; +use cortex_m::peripheral::{DWT, ITM}; +use rtfm::{app, Resource}; + +app! { + device: stm32f103xx, + + resources: { + static ITM: ITM; + }, + + init: { + async_after: [a, b], + }, + + free_interrupts: [EXTI0, EXTI1], + + tasks: { + a: { + async_after: [a], + resources: [ITM], + }, + + b: { + async_after: [b], + priority: 2, + resources: [ITM], + }, + }, +} + +const MS: u32 = 8_000; +const S: u32 = 1_000 * MS; + +#[inline(always)] +fn init(mut ctxt: init::Context) -> init::LateResources { + iprintln!(&mut ctxt.core.ITM.stim[0], "init"); + + ctxt.async.a.post(&mut ctxt.threshold, 2 * S, ()).ok(); + ctxt.async.b.post(&mut ctxt.threshold, 3 * S, ()).ok(); + + init::LateResources { ITM: ctxt.core.ITM } +} + +#[inline(always)] +fn idle(ctxt: idle::Context) -> ! { + loop { + asm::wfi(); + } +} + +fn a(mut ctxt: a::Context) { + let now = DWT::get_cycle_count(); + + let bl = ctxt.baseline; + ctxt.resources.ITM.claim_mut(&mut ctxt.threshold, |itm, _| { + iprintln!(&mut itm.stim[0], "a(bl={}, now={})", bl, now); + }); + + ctxt.async.a.post(&mut ctxt.threshold, 2 * S, ()).ok(); +} + +fn b(mut ctxt: b::Context) { + let now = DWT::get_cycle_count(); + + let bl = ctxt.baseline; + let t = &mut ctxt.threshold; + iprintln!( + &mut ctxt.resources.ITM.borrow_mut(t).stim[0], + "b(bl={}, now={})", + bl, + now + ); + + ctxt.async.b.post(t, 3 * S, ()).ok(); +} diff --git a/examples/periodic.rs b/examples/periodic.rs new file mode 100644 index 0000000000..4125ecaa95 --- /dev/null +++ b/examples/periodic.rs @@ -0,0 +1,77 @@ +// # -Os +// init +// a(bl=8000000, now=8000180) +// a(bl=16000000, now=16000180) +// +// # -O3 +// a(bl=8000000, now=8000168) +// a(bl=16000000, now=16000168) + +#![allow(warnings)] +// #![deny(unsafe_code)] +// #![deny(warnings)] +#![feature(proc_macro)] +#![no_std] + +#[macro_use] +extern crate cortex_m; +extern crate cortex_m_rtfm as rtfm; +// extern crate panic_abort; +extern crate panic_itm; +extern crate stm32f103xx; + +use core::mem; + +use cortex_m::asm; +use cortex_m::peripheral::{DWT, ITM}; +use rtfm::{app, Resource}; + +app! { + device: stm32f103xx, + + resources: { + static ITM: ITM; + }, + + init: { + async_after: [a], + }, + + free_interrupts: [EXTI0], + + tasks: { + a: { + async_after: [a], + resources: [ITM], + }, + }, +} + +const MS: u32 = 8_000; +const S: u32 = 1_000 * MS; + +#[inline(always)] +fn init(mut ctxt: init::Context) -> init::LateResources { + iprintln!(&mut ctxt.core.ITM.stim[0], "init"); + + ctxt.async.a.post(&mut ctxt.threshold, 1 * S, ()).ok(); + + init::LateResources { ITM: ctxt.core.ITM } +} + +#[inline(always)] +fn idle(ctxt: idle::Context) -> ! { + loop { + asm::wfi(); + } +} + +fn a(mut ctxt: a::Context) { + let now = DWT::get_cycle_count(); + + let bl = ctxt.baseline; + let itm = ctxt.resources.ITM; + iprintln!(&mut itm.stim[0], "a(bl={}, now={})", bl, now); + + ctxt.async.a.post(&mut ctxt.threshold, 1 * S, ()).ok(); +} diff --git a/examples/preemption.rs b/examples/preemption.rs deleted file mode 100644 index 8e501887c3..0000000000 --- a/examples/preemption.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Two tasks running at *different* priorities with access to the same resource -#![deny(unsafe_code)] -#![deny(warnings)] -#![feature(proc_macro)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Resource, Threshold}; - -app! { - device: stm32f103xx, - - resources: { - static COUNTER: u64 = 0; - }, - - tasks: { - // The `SYS_TICK` task has higher priority than `TIM2` - SYS_TICK: { - path: sys_tick, - priority: 2, - resources: [COUNTER], - }, - - TIM2: { - path: tim2, - priority: 1, - resources: [COUNTER], - }, - }, -} - -fn init(_p: init::Peripherals, _r: init::Resources) { - // .. -} - -fn idle() -> ! { - loop { - rtfm::wfi(); - } -} - -fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { - // .. - - // This task can't be preempted by `tim2` so it has direct access to the - // resource data - *r.COUNTER += 1; - - // .. -} - -fn tim2(t: &mut Threshold, mut r: TIM2::Resources) { - // .. - - // As this task runs at lower priority it needs a critical section to - // prevent `sys_tick` from preempting it while it modifies this resource - // data. The critical section is required to prevent data races which can - // lead to undefined behavior. - r.COUNTER.claim_mut(t, |counter, _t| { - // `claim_mut` creates a critical section - *counter += 1; - }); - - // .. -} diff --git a/examples/safe-static-mut-ref.rs b/examples/safe-static-mut-ref.rs deleted file mode 100644 index 81dbde26b9..0000000000 --- a/examples/safe-static-mut-ref.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Safe creation of `&'static mut` references -#![deny(unsafe_code)] -#![deny(warnings)] -#![feature(proc_macro)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::app; - -app! { - device: stm32f103xx, - - resources: { - static BUFFER: [u8; 16] = [0; 16]; - }, - - init: { - resources: [BUFFER], - }, -} - -fn init(_p: init::Peripherals, r: init::Resources) { - let _buf: &'static mut [u8; 16] = r.BUFFER; -} - -fn idle() -> ! { - loop { - rtfm::wfi(); - } -} diff --git a/examples/tq.rs b/examples/tq.rs deleted file mode 100644 index 36dda82d78..0000000000 --- a/examples/tq.rs +++ /dev/null @@ -1,273 +0,0 @@ -// #![deny(unsafe_code)] -// #![deny(warnings)] -#![allow(dead_code)] -#![feature(proc_macro)] -#![no_std] - -#[macro_use] -extern crate cortex_m; -extern crate cortex_m_rtfm as rtfm; -extern crate panic_abort; -extern crate stm32f103xx; - -use core::cmp; - -use cortex_m::peripheral::syst::SystClkSource; -use cortex_m::peripheral::ITM; -use rtfm::ll::{self, Consumer, FreeList, Instant, Node, Producer, RingBuffer, Slot, TaggedPayload, - TimerQueue}; -use rtfm::{app, Resource, Threshold}; -use stm32f103xx::Interrupt; - -const ACAP: usize = 4; - -const MS: u32 = 8_000; - -app! { - device: stm32f103xx, - - resources: { - /* timer queue */ - static TQ: TimerQueue; ACAP]>; - - /* a */ - // payloads w/ after - static AN: [Node; ACAP] = unsafe { ll::uninitialized() }; - static AFL: FreeList = FreeList::new(); - - /* exti0 */ - static Q1: RingBuffer, [TaggedPayload; ACAP + 1], u8> = - RingBuffer::u8(); - static Q1C: Consumer<'static, TaggedPayload, [TaggedPayload; ACAP + 1], u8>; - static Q1P: Producer<'static, TaggedPayload, [TaggedPayload; ACAP + 1], u8>; - }, - - init: { - resources: [AN, Q1], - }, - - tasks: { - EXTI1: { - path: exti1, - resources: [TQ, AFL], - priority: 1, - - // async: [a], - }, - - // dispatch interrupt - EXTI0: { - path: exti0, - resources: [Q1C, AFL], - priority: 3, - }, - - // timer queue - SYS_TICK: { - path: sys_tick, - resources: [TQ, Q1P], - priority: 2, - }, - }, -} - -pub fn init(mut p: ::init::Peripherals, r: init::Resources) -> init::LateResources { - // .. - - /* executed after `init` end */ - p.core.DWT.enable_cycle_counter(); - unsafe { p.core.DWT.cyccnt.write(0) }; - p.core.SYST.set_clock_source(SystClkSource::Core); - p.core.SYST.enable_counter(); - p.core.SYST.disable_interrupt(); - - // populate the free list - for n in r.AN { - r.AFL.push(Slot::new(n)); - } - - let (q1p, q1c) = r.Q1.split(); - init::LateResources { - TQ: TimerQueue::new(p.core.SYST), - Q1C: q1c, - Q1P: q1p, - } -} - -pub fn idle() -> ! { - rtfm::set_pending(Interrupt::EXTI1); - - loop { - rtfm::wfi() - } -} - -fn a(_t: &mut Threshold, bl: Instant, payload: i32) { - unsafe { - iprintln!( - &mut (*ITM::ptr()).stim[0], - "a(now={:?}, bl={:?}, payload={})", - Instant::now(), - bl, - payload - ) - } -} - -fn exti1(t: &mut Threshold, r: EXTI1::Resources) { - /* expansion */ - let bl = Instant::now(); - let mut async = a::Async::new(bl, r.TQ, r.AFL); - /* end of expansion */ - - unsafe { iprintln!(&mut (*ITM::ptr()).stim[0], "EXTI0(bl={:?})", bl) } - async.a(t, 100 * MS, 0).unwrap(); - async.a(t, 50 * MS, 1).unwrap(); - async.a(t, 75 * MS, 2).unwrap(); - async.a(t, 75 * MS + 1, 3).unwrap(); -} - -/* auto generated */ -fn exti0(t: &mut Threshold, mut r: EXTI0::Resources) { - while let Some(payload) = r.Q1C.dequeue() { - match payload.tag() { - Task1::a => { - let (bl, payload, slot) = unsafe { payload.coerce() }.read(); - - r.AFL.claim_mut(t, |afl, _| afl.push(slot)); - - a(&mut unsafe { Threshold::new(3) }, bl, payload); - } - } - } -} - -fn sys_tick(t: &mut Threshold, r: SYS_TICK::Resources) { - #[allow(non_snake_case)] - let SYS_TICK::Resources { mut Q1P, mut TQ } = r; - - enum State - where - T: Copy, - { - Payload(TaggedPayload), - Baseline(Instant), - Done, - } - - loop { - let state = TQ.claim_mut(t, |tq, _| { - if let Some(bl) = tq.queue.peek().map(|p| p.baseline()) { - if Instant::now() >= bl { - // message ready - State::Payload(tq.queue.pop().unwrap()) - } else { - // new timeout - State::Baseline(bl) - } - } else { - // empty queue - tq.syst.disable_interrupt(); - State::Done - } - }); - - match state { - State::Payload(p) => match p.tag() { - Task::a => { - Q1P.claim_mut(t, |q1p, _| q1p.enqueue_unchecked(p.retag(Task1::a))); - rtfm::set_pending(Interrupt::EXTI0); - } - }, - State::Baseline(bl) => { - const MAX: u32 = 0x00ffffff; - - let diff = bl - Instant::now(); - - if diff < 0 { - // message became ready - continue; - } else { - TQ.claim_mut(t, |tq, _| { - tq.syst.set_reload(cmp::min(MAX, diff as u32)); - tq.syst.clear_current(); - }); - return; - } - } - State::Done => { - return; - } - } - } -} - -// Tasks dispatched at a priority of 1 -#[allow(non_camel_case_types)] -#[derive(Clone, Copy)] -pub enum Task1 { - a, -} - -// All tasks -#[allow(non_camel_case_types)] -#[derive(Clone, Copy)] -pub enum Task { - a, -} - -mod a { - use cortex_m::peripheral::SCB; - - use rtfm::ll::Instant; - use rtfm::{Resource, Threshold}; - use Task; - - #[allow(non_snake_case)] - pub struct Async { - // inherited baseline - baseline: Instant, - TQ: ::EXTI1::TQ, - AFL: ::EXTI1::AFL, - } - - impl Async { - #[allow(non_snake_case)] - pub fn new(bl: Instant, TQ: ::EXTI1::TQ, AFL: ::EXTI1::AFL) -> Self { - Async { - baseline: bl, - TQ, - AFL, - } - } - - pub fn a(&mut self, t: &mut Threshold, after: u32, payload: i32) -> Result<(), i32> { - if let Some(slot) = self.AFL.claim_mut(t, |afl, _| afl.pop()) { - let baseline = self.baseline; - self.TQ.claim_mut(t, |tq, _| { - if tq.queue.capacity() == tq.queue.len() { - // full - Err(payload) - } else { - let bl = baseline + after; - if tq.queue - .peek() - .map(|head| bl < head.baseline()) - .unwrap_or(true) - { - tq.syst.enable_interrupt(); - // Set SYST pending - unsafe { (*SCB::ptr()).icsr.write(1 << 26) } - } - - tq.queue.push(slot.write(bl, payload).tag(Task::a)).ok(); - - Ok(()) - } - }) - } else { - Err(payload) - } - } - } -} diff --git a/examples/two-tasks.rs b/examples/two-tasks.rs deleted file mode 100644 index 4f567f0cea..0000000000 --- a/examples/two-tasks.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! Two tasks running at the *same* priority with access to the same resource -#![deny(unsafe_code)] -#![deny(warnings)] -#![feature(proc_macro)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Threshold}; - -app! { - device: stm32f103xx, - - resources: { - static COUNTER: u64 = 0; - }, - - // Both SYS_TICK and TIM2 have access to the `COUNTER` data - tasks: { - SYS_TICK: { - path: sys_tick, - resources: [COUNTER], - }, - - TIM2: { - path: tim2, - resources: [COUNTER], - }, - }, -} - -fn init(_p: init::Peripherals, _r: init::Resources) { - // .. -} - -fn idle() -> ! { - loop { - rtfm::wfi(); - } -} - -// As both tasks are running at the same priority one can't preempt the other. -// Thus both tasks have direct access to the resource -fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { - // .. - - *r.COUNTER += 1; - - // .. -} - -fn tim2(_t: &mut Threshold, mut r: TIM2::Resources) { - // .. - - *r.COUNTER += 1; - - // .. -} diff --git a/examples/zero-tasks.rs b/examples/zero-tasks.rs deleted file mode 100644 index b1ebab6f8e..0000000000 --- a/examples/zero-tasks.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Minimal example with zero tasks -#![deny(unsafe_code)] -#![deny(warnings)] -// IMPORTANT always include this feature gate -#![feature(proc_macro)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; // IMPORTANT always do this rename -extern crate stm32f103xx; // the device crate - -// import the procedural macro -use rtfm::app; - -// This macro call indicates that this is a RTFM application -// -// This macro will expand to a `main` function so you don't need to supply -// `main` yourself. -app! { - // this is the path to the device crate - device: stm32f103xx, -} - -// The initialization phase. -// -// This runs first and within a *global* critical section. Nothing can preempt -// this function. -fn init(p: init::Peripherals) { - // This function has access to all the peripherals of the device - p.core.SYST; - p.device.GPIOA; - p.device.RCC; - // .. -} - -// The idle loop. -// -// This runs after `init` and has a priority of 0. All tasks can preempt this -// function. This function can never return so it must contain some sort of -// endless loop. -fn idle() -> ! { - loop { - // This puts the processor to sleep until there's a task to service - rtfm::wfi(); - } -} diff --git a/macros/Cargo.toml b/macros/Cargo.toml index d36499e4bc..fd38f32966 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -13,8 +13,10 @@ version = "0.3.1" failure = "0.1.1" proc-macro2 = "0.3.6" quote = "0.5.1" -rtfm-syntax = "0.3.0" +# rtfm-syntax = "0.3.0" +rtfm-syntax = { git = "https://github.com/japaric/rtfm-syntax", branch = "tq" } syn = "0.13.1" +either = "1.5.0" [lib] proc-macro = true diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs index 666dc306a3..3154b36aa1 100644 --- a/macros/src/analyze.rs +++ b/macros/src/analyze.rs @@ -1,77 +1,260 @@ -use std::cmp; -use std::collections::HashMap; +use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; -use syn::Ident; +use either::Either; +use syn::{Ident, Type}; +use syntax::check::App; -use check::App; +pub fn app(app: &App) -> Context { + let mut async = HashSet::new(); + let mut async_after = HashSet::new(); + let mut dispatchers = HashMap::new(); + let mut triggers = HashMap::new(); + let mut tq = TimerQueue::new(); + let mut free_interrupts = app.free_interrupts.iter().cloned().collect::>(); -pub type Ownerships = HashMap; + async.extend(&app.init.async); + async_after.extend(&app.init.async_after); -pub enum Ownership { - /// Owned or co-owned by tasks that run at the same priority - Owned { priority: u8 }, - /// Shared by tasks that run at different priorities. - /// - /// `ceiling` is the maximum value across all the task priorities - Shared { ceiling: u8 }, -} - -impl Ownership { - pub fn ceiling(&self) -> u8 { - match *self { - Ownership::Owned { priority } => priority, - Ownership::Shared { ceiling } => ceiling, - } - } - - pub fn is_owned(&self) -> bool { - match *self { - Ownership::Owned { .. } => true, - _ => false, - } - } -} - -pub fn app(app: &App) -> Ownerships { - let mut ownerships = HashMap::new(); - - for resource in &app.idle.resources { - ownerships.insert(resource.clone(), Ownership::Owned { priority: 0 }); - } - - for task in app.tasks.values() { - for resource in task.resources.iter() { - if let Some(ownership) = ownerships.get_mut(resource) { - match *ownership { - Ownership::Owned { priority } => { - if priority == task.priority { - *ownership = Ownership::Owned { priority }; - } else { - *ownership = Ownership::Shared { - ceiling: cmp::max(priority, task.priority), - }; - } - } - Ownership::Shared { ceiling } => { - if task.priority > ceiling { - *ownership = Ownership::Shared { - ceiling: task.priority, - }; - } - } - } - - continue; + // compute dispatchers + for (name, task) in &app.tasks { + match task.interrupt_or_capacity { + Either::Left(interrupt) => { + triggers.insert(interrupt, (*name, task.priority)); } + Either::Right(capacity) => { + let dispatcher = dispatchers.entry(task.priority).or_insert(Dispatcher::new( + free_interrupts.pop().expect("not enough free interrupts"), + )); + dispatcher.tasks.push(*name); + dispatcher.capacity += capacity; + } + } - ownerships.insert( - resource.clone(), - Ownership::Owned { - priority: task.priority, - }, - ); + for task in &task.async { + async.insert(*task); + } + + for task in &task.async_after { + async_after.insert(*task); + + // Timer queue + if let Entry::Vacant(entry) = tq.tasks.entry(*task) { + tq.capacity += app.tasks[task].interrupt_or_capacity.right().unwrap(); + entry.insert(app.tasks[task].priority); + } } } - ownerships + // The SysTick exception runs at the highest dispatcher priority + let sys_tick = dispatchers.keys().cloned().max().unwrap_or(1); + + // compute ceilings + let mut ceilings = Ceilings::new(sys_tick); + + // the SysTick interrupt contends for the timer queue (this has been accounted for in the + // `Ceilings` constructor) and for the producer end of all the dispatcher queues (__#N::Q) + for dispatch_priority in dispatchers.keys() { + ceilings + .dispatch_queues + .insert(*dispatch_priority, sys_tick); + } + + // resources + for (priority, resource) in app.idle.resources.iter().map(|res| (0, res)).chain( + app.tasks + .iter() + .flat_map(|(name, task)| task.resources.iter().map(move |res| (task.priority, res))), + ) { + let ceiling = ceilings + .resources + .entry(*resource) + .or_insert(Ceiling::Owned(priority)); + if priority > (*ceiling).into() { + *ceiling = Ceiling::Shared(priority); + } else if priority < (*ceiling).into() && ceiling.is_owned() { + *ceiling = Ceiling::Shared((*ceiling).into()); + } + } + + // async + for (caller_priority, task) in app.tasks + .values() + .flat_map(|caller| caller.async.iter().map(move |task| (caller.priority, task))) + { + // async callers contend for the consumer end of the task slot queue (#task::SQ) and ... + let ceiling = ceilings.slot_queues.entry(*task).or_insert(caller_priority); + + if caller_priority > *ceiling { + *ceiling = caller_priority; + } + + // .. for the producer end of the dispatcher queue (__#dispatch_priority::Q) + let dispatch_priority = app.tasks[task].priority; + let ceiling = ceilings + .dispatch_queues + .entry(dispatch_priority) + .or_insert(dispatch_priority); + + if caller_priority > *ceiling { + *ceiling = caller_priority; + } + } + + // async_after + for (caller_priority, task) in app.tasks.values().flat_map(|caller| { + caller + .async_after + .iter() + .map(move |task| (caller.priority, task)) + }) { + // async_after callers contend for the consumer end of the task slot queue (#task::SQ) and + // ... + let ceiling = ceilings.slot_queues.entry(*task).or_insert(caller_priority); + + if caller_priority > *ceiling { + *ceiling = caller_priority; + } + + // .. for the timer queue + if caller_priority > ceilings.timer_queue { + ceilings.timer_queue = caller_priority; + } + } + + Context { + async, + async_after, + ceilings, + dispatchers, + sys_tick, + triggers, + timer_queue: tq, + } +} + +pub struct Context { + // set of `async` tasks + pub async: HashSet, + // set of `async_after` tasks + pub async_after: HashSet, + pub ceilings: Ceilings, + // Priority:u8 -> Dispatcher + pub dispatchers: HashMap, + // Interrupt:Ident -> Task:Ident + pub triggers: HashMap, + pub timer_queue: TimerQueue, + // priority of the SysTick exception + pub sys_tick: u8, +} + +pub struct TimerQueue { + // Task:Ident -> Priority:u8 + tasks: HashMap, + capacity: u8, +} + +impl TimerQueue { + fn new() -> Self { + TimerQueue { + tasks: HashMap::new(), + capacity: 0, + } + } + + pub fn capacity(&self) -> u8 { + self.capacity + } + + pub fn tasks(&self) -> &HashMap { + &self.tasks + } +} + +pub struct Dispatcher { + capacity: u8, + interrupt: Ident, + tasks: Vec, +} + +impl Dispatcher { + fn new(interrupt: Ident) -> Self { + Dispatcher { + capacity: 0, + interrupt, + tasks: vec![], + } + } + + pub fn capacity(&self) -> u8 { + self.capacity + } + + pub fn interrupt(&self) -> Ident { + self.interrupt + } + + pub fn tasks(&self) -> &[Ident] { + &self.tasks + } +} + +#[derive(Debug)] +pub struct Ceilings { + dispatch_queues: HashMap, + resources: HashMap, + slot_queues: HashMap, + timer_queue: u8, +} + +impl Ceilings { + fn new(sys_tick_priority: u8) -> Self { + Ceilings { + dispatch_queues: HashMap::new(), + resources: HashMap::new(), + slot_queues: HashMap::new(), + timer_queue: sys_tick_priority, + } + } + + pub fn dispatch_queues(&self) -> &HashMap { + &self.dispatch_queues + } + + pub fn resources(&self) -> &HashMap { + &self.resources + } + + pub fn slot_queues(&self) -> &HashMap { + &self.slot_queues + } + + pub fn timer_queue(&self) -> u8 { + self.timer_queue + } +} + +#[derive(Clone, Copy, Debug)] +pub enum Ceiling { + Owned(u8), + Shared(u8), +} + +impl Ceiling { + pub fn is_owned(&self) -> bool { + if let Ceiling::Owned(..) = *self { + true + } else { + false + } + } +} + +impl From for u8 { + fn from(ceiling: Ceiling) -> u8 { + match ceiling { + Ceiling::Owned(prio) => prio, + Ceiling::Shared(ceil) => ceil, + } + } } diff --git a/macros/src/check.rs b/macros/src/check.rs index 4defb46d7d..424c3e8bc5 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -1,95 +1,7 @@ -use std::collections::HashMap; +use syntax::check::App; +use syntax::Result; -use syn::{Ident, Path}; -use syntax::check::{self, Idents, Idle, Init, Statics}; -use syntax::{self, Result}; - -pub struct App { - pub device: Path, - pub idle: Idle, - pub init: Init, - pub resources: Statics, - pub tasks: Tasks, -} - -pub type Tasks = HashMap; - -#[allow(non_camel_case_types)] -pub enum Exception { - PENDSV, - SVCALL, - SYS_TICK, -} - -impl Exception { - pub fn from(s: &str) -> Option { - Some(match s { - "PENDSV" => Exception::PENDSV, - "SVCALL" => Exception::SVCALL, - "SYS_TICK" => Exception::SYS_TICK, - _ => return None, - }) - } - - pub fn nr(&self) -> usize { - match *self { - Exception::PENDSV => 14, - Exception::SVCALL => 11, - Exception::SYS_TICK => 15, - } - } -} - -pub enum Kind { - Exception(Exception), - Interrupt { enabled: bool }, -} - -pub struct Task { - pub kind: Kind, - pub path: Path, - pub priority: u8, - pub resources: Idents, -} - -pub fn app(app: check::App) -> Result { - let app = App { - device: app.device, - idle: app.idle, - init: app.init, - resources: app.resources, - tasks: app.tasks - .into_iter() - .map(|(k, v)| { - let v = ::check::task(k.as_ref(), v)?; - - Ok((k, v)) - }) - .collect::>()?, - }; - - Ok(app) -} - -fn task(name: &str, task: syntax::check::Task) -> Result { - let kind = match Exception::from(name) { - Some(e) => { - ensure!( - task.enabled.is_none(), - "`enabled` field is not valid for exceptions" - ); - - Kind::Exception(e) - } - None => Kind::Interrupt { - enabled: task.enabled.unwrap_or(true), - }, - }; - - Ok(Task { - kind, - path: task.path, - priority: task.priority.unwrap_or(1), - resources: task.resources, - }) +pub fn app(_app: &App) -> Result<()> { + // TODO ??? + Ok(()) } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 728e6133ec..e0cba88efb 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,7 +1,7 @@ -//! Procedural macros of the `cortex-m-rtfm` crate // #![deny(warnings)] +#![allow(warnings)] #![feature(proc_macro)] -#![recursion_limit = "128"] +#![recursion_limit = "256"] #[macro_use] extern crate failure; @@ -10,6 +10,7 @@ extern crate proc_macro2; extern crate syn; #[macro_use] extern crate quote; +extern crate either; extern crate rtfm_syntax as syntax; use proc_macro::TokenStream; @@ -19,154 +20,6 @@ mod analyze; mod check; mod trans; -/// The `app!` macro, a macro used to specify the tasks and resources of a RTFM application. -/// -/// The contents of this macro uses a `key: value` syntax. All the possible keys are shown below: -/// -/// ``` text -/// app! { -/// device: .., -/// -/// resources: { .. }, -/// -/// init: { .. }, -/// -/// idle: { .. }, -/// -/// tasks: { .. }, -/// } -/// ``` -/// -/// # `device` -/// -/// The value of this key is a Rust path, like `foo::bar::baz`, that must point to a *device crate*, -/// a crate generated using `svd2rust`. -/// -/// # `resources` -/// -/// This key is optional. Its value is a list of `static` variables. These variables are the data -/// that can be safely accessed, modified and shared by tasks. -/// -/// ``` text -/// resources: { -/// static A: bool = false; -/// static B: i32 = 0; -/// static C: [u8; 16] = [0; 16]; -/// static D: Thing = Thing::new(..); -/// static E: Thing; -/// } -/// ``` -/// -/// The initial value of a resource can be omitted. This means that the resource will be runtime -/// initialized; these runtime initialized resources are also known as *late resources*. -/// -/// If this key is omitted its value defaults to an empty list. -/// -/// # `init` -/// -/// This key is optional. Its value is a set of key values. All the possible keys are shown below: -/// -/// ``` text -/// init: { -/// path: .., -/// } -/// ``` -/// -/// ## `init.path` -/// -/// This key is optional. Its value is a Rust path, like `foo::bar::baz`, that points to the -/// initialization function. -/// -/// If the key is omitted its value defaults to `init`. -/// -/// ## `init.resources` -/// -/// This key is optional. Its value is a set of resources the `init` function *owns*. The resources -/// in this list must be a subset of the resources listed in the top `resources` key. Note that some -/// restrictions apply: -/// -/// - The resources in this list can't be late resources. -/// - The resources that appear in this list can't appear in other list like `idle.resources` or -/// `tasks.$TASK.resources` -/// -/// If this key is omitted its value is assumed to be an empty list. -/// -/// # `idle` -/// -/// This key is optional. Its value is a set of key values. All the possible keys are shown below: -/// -/// ``` text -/// idle: { -/// path: .., -/// resources: [..], -/// } -/// ``` -/// -/// ## `idle.path` -/// -/// This key is optional. Its value is a Rust path, like `foo::bar::baz`, that points to the idle -/// loop function. -/// -/// If the key is omitted its value defaults to `idle`. -/// -/// ## `idle.resources` -/// -/// This key is optional. Its value is a list of resources the `idle` loop has access to. The -/// resources in this list must be a subset of the resources listed in the top `resources` key. -/// -/// If omitted its value defaults to an empty list. -/// -/// # `tasks` -/// -/// This key is optional. Its value is a list of tasks. Each task itself is a set of key value pair. -/// The full syntax is shown below: -/// -/// ``` text -/// tasks: { -/// $TASK: { -/// enabled: .., -/// path: .., -/// priority: .., -/// resources: [..], -/// }, -/// } -/// ``` -/// -/// If this key is omitted its value is assumed to be an empty list. -/// -/// ## `tasks.$TASK` -/// -/// The key must be either a Cortex-M exception or a device specific interrupt. `PENDSV`, `SVCALL`, -/// `SYS_TICK` are considered as exceptions. All other names are assumed to be interrupts. -/// -/// ## `tasks.$TASK.enabled` -/// -/// This key is optional for interrupts and forbidden for exceptions. Its value must be a boolean -/// and indicates whether the interrupt will be enabled (`true`) or disabled (`false`) after `init` -/// ends and before `idle` starts. -/// -/// If this key is omitted its value defaults to `true`. -/// -/// ## `tasks.$TASK.path` -/// -/// The value of this key is a Rust path, like `foo::bar::baz`, that points to the handler of this -/// task. -/// -/// ## `tasks.$TASK.priority` -/// -/// This key is optional. Its value is an integer with type `u8` that specifies the priority of this -/// task. The minimum valid priority is 1. The maximum valid priority depends on the number of the -/// NVIC priority bits the device has; if the device has 4 priority bits the maximum allowed value -/// would be 16. -/// -/// If this key is omitted its value defaults to `1`. -/// -/// ## `tasks.$TASK.resources` -/// -/// This key is optional. Its value is a list of resources this task has access to. The resources in -/// this list must be a subset of the resources listed in the top `resources` key. -/// -/// If omitted its value defaults to an empty list. #[proc_macro] pub fn app(ts: TokenStream) -> TokenStream { match run(ts) { @@ -177,10 +30,10 @@ pub fn app(ts: TokenStream) -> TokenStream { fn run(ts: TokenStream) -> Result { let app = App::parse(ts)?.check()?; - let app = check::app(app)?; + check::app(&app)?; - let ownerships = analyze::app(&app); - let tokens = trans::app(&app, &ownerships); + let ctxt = analyze::app(&app); + let tokens = trans::app(&ctxt, &app); Ok(tokens.into()) } diff --git a/macros/src/trans.rs b/macros/src/trans.rs index 964b1a3081..6f359b4073 100644 --- a/macros/src/trans.rs +++ b/macros/src/trans.rs @@ -1,632 +1,817 @@ -use proc_macro2::Span; use quote::Tokens; -use syn::{Ident, LitStr}; -use analyze::Ownerships; -use check::{App, Kind}; +use either::Either; +use syn::Ident; +use syntax::check::App; -fn krate() -> Ident { - Ident::from("rtfm") -} +use analyze::Context; -pub fn app(app: &App, ownerships: &Ownerships) -> Tokens { +pub fn app(ctxt: &Context, app: &App) -> Tokens { let mut root = vec![]; - let mut main = vec![quote!(#![allow(path_statements)])]; + let krate = Ident::from("cortex_m_rtfm"); + let device = &app.device; + let hidden = Ident::from("__hidden"); - ::trans::tasks(app, ownerships, &mut root, &mut main); - ::trans::init(app, &mut main, &mut root); - ::trans::idle(app, ownerships, &mut main, &mut root); - ::trans::resources(app, ownerships, &mut root); + let needs_tq = !ctxt.async_after.is_empty(); + /* root */ + // NOTE we can't use paths like `#krate::foo` in the root because there's no guarantee that the + // user has not renamed `cortex_m_rtfm` (e.g. `extern crate cortex_m_rtfm as rtfm`) so instead + // we add this `#hidden` module and use `#hidden::#krate::foo` in the root. root.push(quote! { - #[allow(unsafe_code)] - fn main() { - #(#main)* + mod #hidden { + pub extern crate #krate; } }); - quote!(#(#root)*) -} - -fn idle(app: &App, ownerships: &Ownerships, main: &mut Vec, root: &mut Vec) { - let krate = krate(); - - let mut mod_items = vec![]; - let mut tys = vec![]; - let mut exprs = vec![]; - - if !app.idle.resources.is_empty() { - tys.push(quote!(&mut #krate::Threshold)); - exprs.push(quote!(unsafe { &mut #krate::Threshold::new(0) })); - } - - if !app.idle.resources.is_empty() { - let mut needs_reexport = false; - for name in &app.idle.resources { - if ownerships[name].is_owned() { - if app.resources.get(name).is_some() { - needs_reexport = true; - break; - } - } - } - - let super_ = if needs_reexport { - None - } else { - Some(Ident::from("super")) - }; - let mut rexprs = vec![]; - let mut rfields = vec![]; - for name in &app.idle.resources { - if ownerships[name].is_owned() { - let resource = app.resources.get(name).expect(&format!( - "BUG: resource {} assigned to `idle` has no definition", - name - )); - let ty = &resource.ty; - - rfields.push(quote! { - pub #name: &'static mut #ty, - }); - - let _name = Ident::from(format!("_{}", name.as_ref())); - rexprs.push(if resource.expr.is_some() { - quote! { - #name: &mut #super_::#_name, - } - } else { - quote! { - #name: #super_::#_name.as_mut(), - } - }); - } else { - rfields.push(quote! { - pub #name: ::idle::#name, - }); - - rexprs.push(quote! { - #name: ::idle::#name { _0: core::marker::PhantomData }, - }); - } - } - - if needs_reexport { - root.push(quote! { - #[allow(non_camel_case_types)] - #[allow(non_snake_case)] - pub struct _idleResources { - #(#rfields)* - } - }); - - mod_items.push(quote! { - pub use ::_idleResources as Resources; - }); - } else { - mod_items.push(quote! { - #[allow(non_snake_case)] - pub struct Resources { - #(#rfields)* - } - }); - } - - mod_items.push(quote! { - #[allow(unsafe_code)] - impl Resources { - pub unsafe fn new() -> Self { - Resources { - #(#rexprs)* - } - } - } - }); - - tys.push(quote!(idle::Resources)); - exprs.push(quote!(unsafe { idle::Resources::new() })); - } - - let device = &app.device; - for name in &app.idle.resources { - let ceiling = ownerships[name].ceiling(); - - // owned resource - if ceiling == 0 { - continue; - } - - let _name = Ident::from(format!("_{}", name.as_ref())); - let resource = app.resources - .get(name) - .expect(&format!("BUG: resource {} has no definition", name)); - + /* Resources */ + let mut resources = vec![]; + for (name, resource) in &app.resources { let ty = &resource.ty; - let _static = if resource.expr.is_some() { - quote!(#_name) - } else { - quote!(#_name.some) - }; - - mod_items.push(quote! { - #[allow(non_camel_case_types)] - pub struct #name { _0: core::marker::PhantomData<*const ()> } - }); + let expr = resource + .expr + .as_ref() + .map(|e| quote!(#e)) + .unwrap_or_else(|| quote!(unsafe { #hidden::#krate::uninitialized() })); + let ceiling = Ident::from(format!( + "U{}", + ctxt.ceilings + .resources() + .get(name) + .cloned() + .map(u8::from) + .unwrap_or(0) // 0 = resource owned by `init` + )); root.push(quote! { - #[allow(unsafe_code)] - unsafe impl #krate::Resource for idle::#name { + unsafe impl #hidden::#krate::Resource for __resource::#name { + const NVIC_PRIO_BITS: u8 = ::#device::NVIC_PRIO_BITS; + type Ceiling = #hidden::#krate::#ceiling; type Data = #ty; - fn borrow<'cs>(&'cs self, t: &'cs Threshold) -> &'cs Self::Data { - assert!(t.value() >= #ceiling); - - unsafe { &#_static } - } - - fn borrow_mut<'cs>( - &'cs mut self, - t: &'cs Threshold, - ) -> &'cs mut Self::Data { - assert!(t.value() >= #ceiling); - - unsafe { &mut #_static } - } - - fn claim(&self, t: &mut Threshold, f: F) -> R - where - F: FnOnce(&Self::Data, &mut Threshold) -> R - { - unsafe { - #krate::claim( - &#_static, - #ceiling, - #device::NVIC_PRIO_BITS, - t, - f, - ) - } - } - - fn claim_mut(&mut self, t: &mut Threshold, f: F) -> R - where - F: FnOnce(&mut Self::Data, &mut Threshold) -> R - { - unsafe { - #krate::claim( - &mut #_static, - #ceiling, - #device::NVIC_PRIO_BITS, - t, - f, - ) - } - } - } - }); - } - - if !mod_items.is_empty() { - root.push(quote! { - #[allow(unsafe_code)] - mod idle { - #(#mod_items)* - } - }); - } - - let idle = &app.idle.path; - main.push(quote! { - // type check - let idle: fn(#(#tys),*) -> ! = #idle; - - idle(#(#exprs),*); - }); -} - -fn init(app: &App, main: &mut Vec, root: &mut Vec) { - let device = &app.device; - let krate = krate(); - - let mut tys = vec![quote!(init::Peripherals)]; - let mut exprs = vec![ - quote!{ - init::Peripherals { - core: ::#device::CorePeripherals::steal(), - device: ::#device::Peripherals::steal(), - } - }, - ]; - let mut ret = None; - let mut mod_items = vec![]; - - let (init_resources, late_resources): (Vec<_>, Vec<_>) = app.resources - .iter() - .partition(|&(_, res)| res.expr.is_some()); - - if !init_resources.is_empty() { - let mut fields = vec![]; - let mut lifetime = None; - let mut rexprs = vec![]; - - for (name, resource) in init_resources { - let ty = &resource.ty; - - if app.init.resources.contains(name) { - fields.push(quote! { - pub #name: &'static mut #ty, - }); - - let expr = &resource.expr; - rexprs.push(quote!(#name: { + unsafe fn get() -> &'static mut Self::Data { static mut #name: #ty = #expr; + &mut #name - },)); - } else { - let _name = Ident::from(format!("_{}", name.as_ref())); - lifetime = Some(quote!('a)); - - fields.push(quote! { - pub #name: &'a mut #ty, - }); - - rexprs.push(quote! { - #name: &mut ::#_name, - }); - } - } - - root.push(quote! { - #[allow(non_camel_case_types)] - #[allow(non_snake_case)] - pub struct _initResources<#lifetime> { - #(#fields)* + } } }); - mod_items.push(quote! { - pub use ::_initResources as Resources; + resources.push(quote! { + pub struct #name { _0: () } + + impl #name { + pub unsafe fn new() -> Self { + #name { _0: () } + } + } + }); + } + + root.push(quote! { + mod __resource { + extern crate #krate; + + #(#resources)* + } + }); + + /* Tasks */ + for (name, task) in &app.tasks { + let path = &task.path; + let input = &task.input; + + let lifetime = if task.resources + .iter() + .any(|res| ctxt.ceilings.resources()[res].is_owned()) + { + Some(quote!('a)) + } else { + None + }; + + let __context = Ident::from(format!("__{}_Context", name)); + + let mut mod_ = vec![]; + + // NOTE some stuff has to go in the root because `#input` is not guaranteed to be a + // primitive type and there's no way to import that type into a module (we don't know its + // full path). So instead we just assume that `#input` has been imported in the root; this + // forces us to put anything that refers to `#input` in the root. + root.push(quote! { + pub struct #__context<#lifetime> { + pub async: #name::Async, + pub baseline: u32, + pub input: #input, + pub resources: #name::Resources<#lifetime>, + pub threshold: #hidden::#krate::Threshold<#name::Priority>, + } + + impl<#lifetime> #__context<#lifetime> { + pub unsafe fn new(bl: #hidden::#krate::Instant, payload: #input) -> Self { + #__context { + async: #name::Async::new(bl), + baseline: bl.into(), + input: payload, + resources: #name::Resources::new(), + threshold: #hidden::#krate::Threshold::new(), + } + } + } + }); + + let res_fields = task.resources + .iter() + .map(|res| { + if ctxt.ceilings.resources()[res].is_owned() { + let ty = &app.resources[res].ty; + quote!(pub #res: &'a mut #ty) + } else { + quote!(pub #res: super::__resource::#res) + } + }) + .collect::>(); + + let res_exprs = task.resources.iter().map(|res| { + if ctxt.ceilings.resources()[res].is_owned() { + quote!(#res: super::__resource::#res::get()) + } else { + quote!(#res: super::__resource::#res::new()) + } + }); + + let async_fields = task.async + .iter() + .map(|task| quote!(pub #task: ::__async::#task)) + .chain( + task.async_after + .iter() + .map(|task| quote!(pub #task: ::__async_after::#task)), + ) + .collect::>(); + + let async_exprs = task.async + .iter() + .map(|task| quote!(#task: ::__async::#task::new(bl))) + .chain( + task.async_after + .iter() + .map(|task| quote!(#task: ::__async_after::#task::new(bl))), + ) + .collect::>(); + + let priority = Ident::from(format!("U{}", task.priority)); + mod_.push(quote! { + extern crate #krate; + + pub const HANDLER: fn(Context) = ::#path; + + // The priority at this task is dispatched at + pub type Priority = #krate::#priority; + + pub use super::#__context as Context; + + pub struct Async { + #(#async_fields,)* + } + + impl Async { + pub unsafe fn new(bl: #krate::Instant) -> Self { + Async { + #(#async_exprs,)* + } + } + } + + pub struct Resources<#lifetime> { + #(#res_fields,)* + } - #[allow(unsafe_code)] impl<#lifetime> Resources<#lifetime> { pub unsafe fn new() -> Self { Resources { - #(#rexprs)* + #(#res_exprs,)* } } } }); - tys.push(quote!(init::Resources)); - exprs.push(quote!(init::Resources::new())); - } - - // Initialization statements for late resources - let mut late_resource_init = vec![]; - - if !late_resources.is_empty() { - // `init` must initialize and return resources - - let mut fields = vec![]; - - for (name, resource) in late_resources { - let _name = Ident::from(format!("_{}", name.as_ref())); - - let ty = &resource.ty; - - fields.push(quote! { - pub #name: #ty, - }); - - late_resource_init.push(quote! { - #_name = #krate::UntaggedOption { some: _late_resources.#name }; - }); - } - - root.push(quote! { - #[allow(non_camel_case_types)] - #[allow(non_snake_case)] - pub struct _initLateResources { - #(#fields)* - } - }); - - mod_items.push(quote! { - pub use ::_initLateResources as LateResources; - }); - - // `init` must return the initialized resources - ret = Some(quote!( -> ::init::LateResources)); - } - - root.push(quote! { - #[allow(unsafe_code)] - mod init { - pub struct Peripherals { - pub core: ::#device::CorePeripherals, - pub device: ::#device::Peripherals, - } - - #(#mod_items)* - } - }); - - let mut exceptions = vec![]; - let mut interrupts = vec![]; - for (name, task) in &app.tasks { - match task.kind { - Kind::Exception(ref e) => { - if exceptions.is_empty() { - exceptions.push(quote! { - let scb = &*#device::SCB::ptr(); - }); - } - - let nr = e.nr(); - let priority = task.priority; - exceptions.push(quote! { - let prio_bits = #device::NVIC_PRIO_BITS; - let hw = ((1 << prio_bits) - #priority) << (8 - prio_bits); - scb.shpr[#nr - 4].write(hw); - }); - } - Kind::Interrupt { enabled } => { - // Interrupt. These are enabled / disabled through the NVIC - if interrupts.is_empty() { - interrupts.push(quote! { - use #device::Interrupt; - - let mut nvic: #device::NVIC = core::mem::transmute(()); - }); - } - - let priority = task.priority; - interrupts.push(quote! { - let prio_bits = #device::NVIC_PRIO_BITS; - let hw = ((1 << prio_bits) - #priority) << (8 - prio_bits); - nvic.set_priority(Interrupt::#name, hw); - }); - - if enabled { - interrupts.push(quote! { - nvic.enable(Interrupt::#name); - }); - } else { - interrupts.push(quote! { - nvic.disable(Interrupt::#name); - }); - } - } - } - } - - let init = &app.init.path; - main.push(quote! { - // type check - let init: fn(#(#tys,)*) #ret = #init; - - #krate::atomic(unsafe { &mut #krate::Threshold::new(0) }, |_t| unsafe { - let _late_resources = init(#(#exprs,)*); - #(#late_resource_init)* - - #(#exceptions)* - #(#interrupts)* - }); - }); -} - -fn resources(app: &App, ownerships: &Ownerships, root: &mut Vec) { - let krate = krate(); - - for name in ownerships.keys() { - let _name = Ident::from(format!("_{}", name.as_ref())); - - // Declare the static that holds the resource - let resource = app.resources - .get(name) - .expect(&format!("BUG: resource {} has no definition", name)); - - let expr = &resource.expr; - let ty = &resource.ty; - - root.push(match *expr { - Some(ref expr) => quote! { - static mut #_name: #ty = #expr; - }, - None => quote! { - // Resource initialized in `init` - static mut #_name: #krate::UntaggedOption<#ty> = - #krate::UntaggedOption { none: () }; - }, - }); - } -} - -fn tasks(app: &App, ownerships: &Ownerships, root: &mut Vec, main: &mut Vec) { - let device = &app.device; - let krate = krate(); - - for (tname, task) in &app.tasks { - let mut exprs = vec![]; - let mut fields = vec![]; - let mut items = vec![]; - - let has_resources = !task.resources.is_empty(); - - if has_resources { - for rname in &task.resources { - let ceiling = ownerships[rname].ceiling(); - let _rname = Ident::from(format!("_{}", rname.as_ref())); - let resource = app.resources - .get(rname) - .expect(&format!("BUG: resource {} has no definition", rname)); - - let ty = &resource.ty; - let _static = if resource.expr.is_some() { - quote!(#_rname) - } else { - quote!(#_rname.some) - }; - - items.push(quote! { - #[allow(non_camel_case_types)] - pub struct #rname { _0: PhantomData<*const ()> } - }); + match task.interrupt_or_capacity { + Either::Left(interrupt) => { + let export_name = interrupt.as_ref(); + let fn_name = Ident::from(format!("__{}", interrupt)); root.push(quote! { - #[allow(unsafe_code)] - unsafe impl #krate::Resource for #tname::#rname { - type Data = #ty; + #[export_name = #export_name] + pub unsafe extern "C" fn #fn_name() { + let _ = #device::Interrupt::#interrupt; // verify that the interrupt exists + #name::HANDLER(#name::Context::new(#hidden::#krate::Instant::now(), ())) + } + }); + } + Either::Right(capacity) => { + let capacity = Ident::from(format!("U{}", capacity)); - fn borrow<'cs>(&'cs self, t: &'cs Threshold) -> &'cs Self::Data { - assert!(t.value() >= #ceiling); + root.push(quote! { + unsafe impl #hidden::#krate::Resource for #name::SQ { + const NVIC_PRIO_BITS: u8 = ::#device::NVIC_PRIO_BITS; + type Ceiling = #name::Ceiling; + type Data = #hidden::#krate::SlotQueue<#input, #hidden::#krate::#capacity>; - unsafe { &#_static } + unsafe fn get() -> &'static mut Self::Data { + static mut SQ: + #hidden::#krate::SlotQueue<#input, #hidden::#krate::#capacity> = + #hidden::#krate::SlotQueue::u8(); + + &mut SQ } + } - fn borrow_mut<'cs>( - &'cs mut self, - t: &'cs Threshold, - ) -> &'cs mut Self::Data { - assert!(t.value() >= #ceiling); + }); - unsafe { &mut #_static } + let ceiling = Ident::from(format!( + "U{}", + ctxt.ceilings.slot_queues().get(name).cloned() // 0 = owned by init + .unwrap_or(0) + )); + mod_.push(quote! { + pub struct SQ { _0: () } + + impl SQ { + pub unsafe fn new() -> Self { + SQ { _0: () } } + } - fn claim(&self, t: &mut Threshold, f: F) -> R - where - F: FnOnce(&Self::Data, &mut Threshold) -> R - { - unsafe { - #krate::claim( - &#_static, - #ceiling, - #device::NVIC_PRIO_BITS, - t, - f, - ) - } - } + // Ceiling of the `SQ` resource + pub type Ceiling = #krate::#ceiling; + }); + } + } - fn claim_mut(&mut self, t: &mut Threshold, f: F) -> R - where - F: FnOnce(&mut Self::Data, &mut Threshold) -> R - { - unsafe { - #krate::claim( - &mut #_static, - #ceiling, - #device::NVIC_PRIO_BITS, - t, - f, - ) + root.push(quote! { + mod #name { + #(#mod_)* + } + }); + } + + /* Async */ + let async = ctxt.async + .iter() + .map(|name| { + let task = &app.tasks[name]; + let priority = task.priority; + let __priority = Ident::from(format!("__{}", priority)); + let ty = &task.input; + + let sqc = Ident::from(format!( + "U{}", + ctxt.ceilings.slot_queues().get(name).cloned() // 0 = owned by init + .unwrap_or(0) + )); + let qc = Ident::from(format!("U{}", ctxt.ceilings.dispatch_queues()[&priority])); + + quote! { + pub struct #name { baseline: #krate::Instant } + + impl #name { + pub unsafe fn new(bl: #krate::Instant) -> Self { + #name { baseline: bl } + } + + // XXX or take `self`? + #[inline] + pub fn post

( + &self, + t: &mut #krate::Threshold

, + payload: #ty, + ) -> Result<(), #ty> + where + P: #krate::Unsigned + + #krate::Max<#krate::#sqc> + + #krate::Max<#krate::#qc>, + #krate::Maximum: #krate::Unsigned, + #krate::Maximum: #krate::Unsigned, + { + unsafe { + use self::#krate::Resource; + + if let Some(slot) = + ::#name::SQ::new().claim_mut(t, |sq, _| sq.dequeue()) { + let tp = slot + .write(self.baseline, payload) + .tag(::#__priority::Task::#name); + + ::#__priority::Q::new().claim_mut(t, |q, _| { + q.split().0.enqueue_unchecked(tp); + }); + + Ok(()) + } else { + Err(payload) } } } - }); + } + } + }) + .collect::>(); + root.push(quote! { + mod __async { + extern crate #krate; - if ceiling <= task.priority { - root.push(quote! { - #[allow(unsafe_code)] - impl core::ops::Deref for #tname::#rname { - type Target = #ty; + #(#async)* + } + }); - fn deref(&self) -> &Self::Target { - unsafe { &#_static } + /* Async (+after) */ + let async_after = ctxt.async_after + .iter() + .map(|name| { + let task = &app.tasks[name]; + let ty = &task.input; + + let sqc = Ident::from(format!("U{}", ctxt.ceilings.slot_queues()[name])); + let tqc = Ident::from(format!("U{}", ctxt.ceilings.timer_queue())); + + quote! { + pub struct #name { baseline: #krate::Instant } + + impl #name { + pub unsafe fn new(bl: #krate::Instant) -> Self { + #name { baseline: bl } + } + + // XXX or take `self`? + #[inline] + pub fn post

( + &self, + t: &mut #krate::Threshold

, + after: u32, + payload: #ty, + ) -> Result<(), #ty> + where + P: #krate::Unsigned + + #krate::Max<#krate::#sqc> + + #krate::Max<#krate::#tqc>, + #krate::Maximum: #krate::Unsigned, + #krate::Maximum: #krate::Unsigned, + { + unsafe { + use self::#krate::Resource; + + if let Some(slot) = + ::#name::SQ::new().claim_mut(t, |sq, _| sq.dequeue()) { + let bl = self.baseline + after; + let tp = slot + .write(bl, payload) + .tag(::__tq::Task::#name); + + ::__tq::TQ::new().claim_mut(t, |tq, _| tq.enqueue(bl, tp)); + + Ok(()) + } else { + Err(payload) } } + } + } + } + }) + .collect::>(); + root.push(quote! { + mod __async_after { + extern crate #krate; - #[allow(unsafe_code)] - impl core::ops::DerefMut for #tname::#rname { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut #_static } - } + #(#async_after)* + } + }); + + /* Timer queue */ + if needs_tq { + let capacity = Ident::from(format!("U{}", ctxt.timer_queue.capacity())); + let tasks = ctxt.timer_queue.tasks().keys(); + let arms = ctxt.timer_queue + .tasks() + .iter() + .map(|(name, priority)| { + let __priority = Ident::from(format!("__{}", priority)); + let interrupt = ctxt.dispatchers[priority].interrupt(); + + quote! { + __tq::Task::#name => { + #__priority::Q::new().claim_mut(t, |q, _| { + q.split().0.enqueue_unchecked(tp.retag(#__priority::Task::#name)) + }); + #hidden::#krate::set_pending(#device::Interrupt::#interrupt); + } + } + }) + .collect::>(); + + let ceiling = Ident::from(format!("U{}", ctxt.ceilings.timer_queue())); + let priority = Ident::from(format!("U{}", ctxt.sys_tick)); + root.push(quote! { + mod __tq { + extern crate #krate; + + pub struct TQ { _0: () } + + impl TQ { + pub unsafe fn new() -> Self { + TQ { _0: () } + } + } + + unsafe impl #krate::Resource for TQ { + const NVIC_PRIO_BITS: u8 = ::#device::NVIC_PRIO_BITS; + type Ceiling = #krate::#ceiling; + type Data = #krate::TimerQueue; + + unsafe fn get() -> &'static mut Self::Data { + static mut TQ: #krate::TimerQueue = + unsafe { #krate::uninitialized() }; + + &mut TQ + } + } + + // SysTick priority + pub type Priority = #krate::#priority; + + #[derive(Clone, Copy)] + pub enum Task { #(#tasks,)* } + } + + #[export_name = "SYS_TICK"] + pub unsafe extern "C" fn __SYS_TICK() { + use #hidden::#krate::Resource; + + #hidden::#krate::dispatch( + &mut #hidden::#krate::Threshold::<__tq::Priority>::new(), + &mut __tq::TQ::new(), + |t, tp| { + match tp.tag() { + #(#arms,)* } }) - } - - fields.push(quote! { - pub #rname: #rname, - }); - - exprs.push(quote! { - #rname: #rname { _0: PhantomData }, - }); } + }); + } - items.push(quote! { - #[allow(non_snake_case)] - pub struct Resources { - #(#fields)* - } - }); + /* Dispatchers */ + for (priority, dispatcher) in &ctxt.dispatchers { + let __priority = Ident::from(format!("__{}", priority)); + let capacity = Ident::from(format!("U{}", dispatcher.capacity())); + let tasks = dispatcher.tasks(); + let ceiling = Ident::from(format!("U{}", ctxt.ceilings.dispatch_queues()[priority])); - items.push(quote! { - #[allow(unsafe_code)] - impl Resources { + root.push(quote! { + mod #__priority { + extern crate #krate; + + pub struct Q { _0: () } + + impl Q { pub unsafe fn new() -> Self { - Resources { - #(#exprs)* - } + Q { _0: () } } } - }); - } - let mut tys = vec![]; - let mut exprs = vec![]; + unsafe impl #krate::Resource for Q { + const NVIC_PRIO_BITS: u8 = ::#device::NVIC_PRIO_BITS; + type Ceiling = #krate::#ceiling; + type Data = #krate::PayloadQueue; - let priority = task.priority; - if has_resources { - tys.push(quote!(&mut #krate::Threshold)); - exprs.push(quote! { - &mut if #priority == 1 << #device::NVIC_PRIO_BITS { - #krate::Threshold::new(::core::u8::MAX) - } else { - #krate::Threshold::new(#priority) + unsafe fn get() -> &'static mut Self::Data { + static mut Q: #krate::PayloadQueue = + #krate::PayloadQueue::u8(); + + &mut Q + } } + + #[derive(Clone, Copy)] + pub enum Task { #(#tasks,)* } + } + }); + + let arms = dispatcher + .tasks() + .iter() + .map(|name| { + quote! { + #__priority::Task::#name => { + let (bl, payload, slot) = payload.coerce().read(); + // NOTE(get) only `Slot` producer because a task can only be dispatched at one + // priority + #name::SQ::get().split().0.enqueue_unchecked(slot); + #name::HANDLER(#name::Context::new(bl, payload)); + } + } + }) + .collect::>(); + + let interrupt = dispatcher.interrupt(); + let export_name = interrupt.as_ref(); + let fn_name = Ident::from(format!("__{}", export_name)); + root.push(quote! { + #[export_name = #export_name] + pub unsafe extern "C" fn #fn_name() { + use #hidden::#krate::Resource; + + // NOTE(get) the dispatcher is the only consumer of this queue + while let Some(payload) = #__priority::Q::get().split().1.dequeue() { + match payload.tag() { + #(#arms,)* + } + } + } + }) + } + + /* pre-init */ + // Initialize the slot queues + let mut pre_init = vec![]; + for (name, task) in &app.tasks { + let input = &task.input; + + if let Either::Right(capacity) = task.interrupt_or_capacity { + let capacity = capacity as usize; + + pre_init.push(quote! { + { + static mut N: [#hidden::#krate::Node<#input>; #capacity] = + unsafe { #hidden::#krate::uninitialized() }; + + for node in N.iter_mut() { + #name::SQ::get().split().0.enqueue_unchecked(node.into()); + } + } + }) + } + } + + let prio_bits = quote!(#device::NVIC_PRIO_BITS); + if needs_tq { + let priority = ctxt.sys_tick; + + pre_init.push(quote! { + // Configure the system timer + syst.set_clock_source(#hidden::#krate::SystClkSource::Core); + syst.enable_counter(); + + // Set the priority of the SysTick exception + let priority = ((1 << #prio_bits) - #priority) << (8 - #prio_bits); + core.SCB.shpr[11].write(priority); + + // Initialize the timer queue + core::ptr::write(__tq::TQ::get(), #hidden::#krate::TimerQueue::new(syst)); + }); + } + + /* init */ + let res_fields = app.init + .resources + .iter() + .map(|r| { + let ty = &app.resources[r].ty; + quote!(#r: &'static mut #ty) + }) + .collect::>(); + + let res_exprs = app.init + .resources + .iter() + .map(|r| quote!(#r: __resource::#r::get())) + .collect::>(); + + let async_fields = app.init + .async + .iter() + .map(|task| quote!(pub #task: ::__async::#task)) + .chain( + app.init + .async_after + .iter() + .map(|task| quote!(pub #task: ::__async_after::#task)), + ) + .collect::>(); + + let async_exprs = app.init + .async + .iter() + .map(|task| quote!(#task: ::__async::#task::new(bl))) + .chain( + app.init + .async_after + .iter() + .map(|task| quote!(#task: ::__async_after::#task::new(bl))), + ) + .collect::>(); + + let late_resources = app.resources + .iter() + .filter_map(|(name, res)| { + if res.expr.is_none() { + let ty = &res.ty; + Some(quote!(pub #name: #ty)) + } else { + None + } + }) + .collect::>(); + + root.push(quote! { + pub struct _ZN4init13LateResourcesE { + #(#late_resources,)* + } + + mod init { + extern crate #krate; + + pub use ::#device::Peripherals as Device; + pub use ::_ZN4init13LateResourcesE as LateResources; + + pub struct Context { + pub async: Async, + pub baseline: u32, + pub core: #krate::Core, + pub device: Device, + pub resources: Resources, + pub threshold: #krate::Threshold<#krate::U255>, + } + + impl Context { + pub unsafe fn new(core: #krate::Core) -> Self { + Context { + async: Async::new(), + baseline: 0, + core, + device: Device::steal(), + resources: Resources::new(), + threshold: #krate::Threshold::new(), + } + } + } + + pub struct Async { + #(#async_fields,)* + } + + impl Async { + unsafe fn new() -> Self { + let bl = #krate::Instant::new(0); + + Async { + #(#async_exprs,)* + } + } + } + + pub struct Resources { + #(#res_fields,)* + } + + impl Resources { + unsafe fn new() -> Self { + use self::#krate::Resource; + + Resources { + #(#res_exprs,)* + } + } + } + } + }); + + /* post-init */ + let mut post_init = vec![]; + + // Initialize LateResources + for (name, res) in &app.resources { + if res.expr.is_none() { + post_init.push(quote! { + core::ptr::write(__resource::#name::get(), lr.#name); }); } + } - if has_resources { - tys.push(quote!(#tname::Resources)); - exprs.push(quote!(#tname::Resources::new())); + // Set dispatcher priorities + for (priority, dispatcher) in &ctxt.dispatchers { + let interrupt = dispatcher.interrupt(); + post_init.push(quote! { + let priority = ((1 << #prio_bits) - #priority) << (8 - #prio_bits); + nvic.set_priority(#device::Interrupt::#interrupt, priority); + }); + } + + // Set trigger priorities + for (interrupt, (_, priority)) in &ctxt.triggers { + post_init.push(quote! { + let priority = ((1 << #prio_bits) - #priority) << (8 - #prio_bits); + nvic.set_priority(#device::Interrupt::#interrupt, priority); + }); + } + + // Enable the dispatcher interrupts + for dispatcher in ctxt.dispatchers.values() { + let interrupt = dispatcher.interrupt(); + post_init.push(quote! { + nvic.enable(#device::Interrupt::#interrupt); + }); + } + + // Enable triggers + for interrupt in ctxt.triggers.keys() { + post_init.push(quote! { + nvic.enable(#device::Interrupt::#interrupt); + }); + } + + /* idle */ + let res_fields = app.idle + .resources + .iter() + .map(|res| { + let ty = &app.resources[res].ty; + + quote!(pub #res: &'static mut #ty) + }) + .collect::>(); + + let res_exprs = app.idle + .resources + .iter() + .map(|res| quote!(#res: __resource::#res::get())) + .collect::>(); + + root.push(quote! { + mod idle { + extern crate #krate; + + pub struct Context { + pub resources: Resources, + } + + impl Context { + pub unsafe fn new() -> Self { + Context { + resources: Resources::new(), + } + } + } + + pub struct Resources { + #(#res_fields,)* + } + + impl Resources { + unsafe fn new() -> Self { + use self::#krate::Resource; + + Resources { + #(#res_exprs,)* + } + } + } } + }); - let path = &task.path; - let _tname = Ident::from(format!("_{}", tname)); - let export_name = LitStr::new(tname.as_ref(), Span::call_site()); - root.push(quote! { - #[allow(non_snake_case)] - #[allow(unsafe_code)] - #[export_name = #export_name] - pub unsafe extern "C" fn #_tname() { - let f: fn(#(#tys,)*) = #path; + /* main */ + let idle = &app.idle.path; + let init = &app.init.path; + root.push(quote! { + fn main() { + use #hidden::#krate::Resource; - f(#(#exprs,)*) + unsafe { + let init: fn(init::Context) -> init::LateResources = #init; + let idle: fn(idle::Context) -> ! = #idle; + + #hidden::#krate::interrupt::disable(); + + let (mut core, mut dwt, mut nvic, mut syst) = #hidden::#krate::Core::steal(); + + #(#pre_init)* + + let lr = init(init::Context::new(core)); + + #(#post_init)* + + // Set the system baseline to zero + dwt.enable_cycle_counter(); + dwt.cyccnt.write(0); + + #hidden::#krate::interrupt::enable(); + + idle(idle::Context::new()) } - }); + } + }); - root.push(quote!{ - #[allow(non_snake_case)] - #[allow(unsafe_code)] - mod #tname { - #[allow(unused_imports)] - use core::marker::PhantomData; - - #[allow(dead_code)] - #[deny(const_err)] - pub const CHECK_PRIORITY: (u8, u8) = ( - #priority - 1, - (1 << ::#device::NVIC_PRIO_BITS) - #priority, - ); - - #(#items)* - } - }); - - // after miri landed (?) rustc won't analyze `const` items unless they are used so we force - // evaluation with this path statement - main.push(quote!(#tname::CHECK_PRIORITY;)); + quote! { + #(#root)* } } diff --git a/src/examples/_0_zero_tasks.rs b/src/examples/_0_zero_tasks.rs deleted file mode 100644 index 90f16d485f..0000000000 --- a/src/examples/_0_zero_tasks.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! Minimal example with zero tasks -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! // IMPORTANT always include this feature gate -//! #![feature(proc_macro)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; // IMPORTANT always do this rename -//! extern crate stm32f103xx; // the device crate -//! -//! // import the procedural macro -//! use rtfm::app; -//! -//! // This macro call indicates that this is a RTFM application -//! // -//! // This macro will expand to a `main` function so you don't need to supply -//! // `main` yourself. -//! app! { -//! // this is the path to the device crate -//! device: stm32f103xx, -//! } -//! -//! // The initialization phase. -//! // -//! // This runs first and within a *global* critical section. Nothing can preempt -//! // this function. -//! fn init(p: init::Peripherals) { -//! // This function has access to all the peripherals of the device -//! p.core.SYST; -//! p.device.GPIOA; -//! p.device.RCC; -//! // .. -//! } -//! -//! // The idle loop. -//! // -//! // This runs after `init` and has a priority of 0. All tasks can preempt this -//! // function. This function can never return so it must contain some sort of -//! // endless loop. -//! fn idle() -> ! { -//! loop { -//! // This puts the processor to sleep until there's a task to service -//! rtfm::wfi(); -//! } -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_1_one_task.rs b/src/examples/_1_one_task.rs deleted file mode 100644 index c9004e86ac..0000000000 --- a/src/examples/_1_one_task.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! An application with one task -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![feature(proc_macro)] -//! #![no_std] -//! -//! extern crate cortex_m; -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use cortex_m::peripheral::syst::SystClkSource; -//! use rtfm::{app, Threshold}; -//! use stm32f103xx::GPIOC; -//! -//! app! { -//! device: stm32f103xx, -//! -//! // Here data resources are declared -//! // -//! // Data resources are static variables that are safe to share across tasks -//! resources: { -//! // Declaration of resources looks exactly like declaration of static -//! // variables -//! static ON: bool = false; -//! }, -//! -//! // Here tasks are declared -//! // -//! // Each task corresponds to an interrupt or an exception. Every time the -//! // interrupt or exception becomes *pending* the corresponding task handler -//! // will be executed. -//! tasks: { -//! // Here we declare that we'll use the SYS_TICK exception as a task -//! SYS_TICK: { -//! // Path to the task handler -//! path: sys_tick, -//! -//! // These are the resources this task has access to. -//! // -//! // The resources listed here must also appear in `app.resources` -//! resources: [ON], -//! }, -//! } -//! } -//! -//! fn init(mut p: init::Peripherals, r: init::Resources) { -//! // `init` can modify all the `resources` declared in `app!` -//! r.ON; -//! -//! // power on GPIOC -//! p.device.RCC.apb2enr.modify(|_, w| w.iopcen().enabled()); -//! -//! // configure PC13 as output -//! p.device.GPIOC.bsrr.write(|w| w.bs13().set()); -//! p.device -//! .GPIOC -//! .crh -//! .modify(|_, w| w.mode13().output().cnf13().push()); -//! -//! // configure the system timer to generate one interrupt every second -//! p.core.SYST.set_clock_source(SystClkSource::Core); -//! p.core.SYST.set_reload(8_000_000); // 1s -//! p.core.SYST.enable_interrupt(); -//! p.core.SYST.enable_counter(); -//! } -//! -//! fn idle() -> ! { -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! // This is the task handler of the SYS_TICK exception -//! // -//! // `_t` is the preemption threshold token. We won't use it in this program. -//! // -//! // `r` is the set of resources this task has access to. `SYS_TICK::Resources` -//! // has one field per resource declared in `app!`. -//! #[allow(unsafe_code)] -//! fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { -//! // toggle state -//! *r.ON = !*r.ON; -//! -//! if *r.ON { -//! // set the pin PC13 high -//! // NOTE(unsafe) atomic write to a stateless register -//! unsafe { -//! (*GPIOC::ptr()).bsrr.write(|w| w.bs13().set()); -//! } -//! } else { -//! // set the pin PC13 low -//! // NOTE(unsafe) atomic write to a stateless register -//! unsafe { -//! (*GPIOC::ptr()).bsrr.write(|w| w.br13().reset()); -//! } -//! } -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_2_two_tasks.rs b/src/examples/_2_two_tasks.rs deleted file mode 100644 index cf6b33d638..0000000000 --- a/src/examples/_2_two_tasks.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Two tasks running at the *same* priority with access to the same resource -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![feature(proc_macro)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use rtfm::{app, Threshold}; -//! -//! app! { -//! device: stm32f103xx, -//! -//! resources: { -//! static COUNTER: u64 = 0; -//! }, -//! -//! // Both SYS_TICK and TIM2 have access to the `COUNTER` data -//! tasks: { -//! SYS_TICK: { -//! path: sys_tick, -//! resources: [COUNTER], -//! }, -//! -//! TIM2: { -//! path: tim2, -//! resources: [COUNTER], -//! }, -//! }, -//! } -//! -//! fn init(_p: init::Peripherals, _r: init::Resources) { -//! // .. -//! } -//! -//! fn idle() -> ! { -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! // As both tasks are running at the same priority one can't preempt the other. -//! // Thus both tasks have direct access to the resource -//! fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { -//! // .. -//! -//! *r.COUNTER += 1; -//! -//! // .. -//! } -//! -//! fn tim2(_t: &mut Threshold, mut r: TIM2::Resources) { -//! // .. -//! -//! *r.COUNTER += 1; -//! -//! // .. -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_3_preemption.rs b/src/examples/_3_preemption.rs deleted file mode 100644 index 4360185afc..0000000000 --- a/src/examples/_3_preemption.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Two tasks running at *different* priorities with access to the same resource -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![feature(proc_macro)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use rtfm::{app, Resource, Threshold}; -//! -//! app! { -//! device: stm32f103xx, -//! -//! resources: { -//! static COUNTER: u64 = 0; -//! }, -//! -//! tasks: { -//! // The `SYS_TICK` task has higher priority than `TIM2` -//! SYS_TICK: { -//! path: sys_tick, -//! priority: 2, -//! resources: [COUNTER], -//! }, -//! -//! TIM2: { -//! path: tim2, -//! priority: 1, -//! resources: [COUNTER], -//! }, -//! }, -//! } -//! -//! fn init(_p: init::Peripherals, _r: init::Resources) { -//! // .. -//! } -//! -//! fn idle() -> ! { -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { -//! // .. -//! -//! // This task can't be preempted by `tim2` so it has direct access to the -//! // resource data -//! *r.COUNTER += 1; -//! -//! // .. -//! } -//! -//! fn tim2(t: &mut Threshold, mut r: TIM2::Resources) { -//! // .. -//! -//! // As this task runs at lower priority it needs a critical section to -//! // prevent `sys_tick` from preempting it while it modifies this resource -//! // data. The critical section is required to prevent data races which can -//! // lead to undefined behavior. -//! r.COUNTER.claim_mut(t, |counter, _t| { -//! // `claim_mut` creates a critical section -//! *counter += 1; -//! }); -//! -//! // .. -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_4_nested.rs b/src/examples/_4_nested.rs deleted file mode 100644 index e211cf878c..0000000000 --- a/src/examples/_4_nested.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! Nesting claims and how the preemption threshold works -//! -//! If you run this program you'll hit the breakpoints as indicated by the -//! letters in the comments: A, then B, then C, etc. -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![feature(proc_macro)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use stm32f103xx::Interrupt; -//! use rtfm::{app, Resource, Threshold}; -//! -//! app! { -//! device: stm32f103xx, -//! -//! resources: { -//! static LOW: u64 = 0; -//! static HIGH: u64 = 0; -//! }, -//! -//! tasks: { -//! EXTI0: { -//! path: exti0, -//! priority: 1, -//! resources: [LOW, HIGH], -//! }, -//! -//! EXTI1: { -//! path: exti1, -//! priority: 2, -//! resources: [LOW], -//! }, -//! -//! EXTI2: { -//! path: exti2, -//! priority: 3, -//! resources: [HIGH], -//! }, -//! }, -//! } -//! -//! fn init(_p: init::Peripherals, _r: init::Resources) {} -//! -//! fn idle() -> ! { -//! // A -//! rtfm::bkpt(); -//! -//! // Sets task `exti0` as pending -//! // -//! // Because `exti0` has higher priority than `idle` it will be executed -//! // immediately -//! rtfm::set_pending(Interrupt::EXTI0); // ~> exti0 -//! -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! #[allow(non_snake_case)] -//! fn exti0( -//! t: &mut Threshold, -//! EXTI0::Resources { mut LOW, mut HIGH }: EXTI0::Resources, -//! ) { -//! // Because this task has a priority of 1 the preemption threshold `t` also -//! // starts at 1 -//! -//! // B -//! rtfm::bkpt(); -//! -//! // Because `exti1` has higher priority than `exti0` it can preempt it -//! rtfm::set_pending(Interrupt::EXTI1); // ~> exti1 -//! -//! // A claim creates a critical section -//! LOW.claim_mut(t, |_low, t| { -//! // This claim increases the preemption threshold to 2 -//! // -//! // 2 is just high enough to not race with task `exti1` for access to the -//! // `LOW` resource -//! -//! // D -//! rtfm::bkpt(); -//! -//! // Now `exti1` can't preempt this task because its priority is equal to -//! // the current preemption threshold -//! rtfm::set_pending(Interrupt::EXTI1); -//! -//! // But `exti2` can, because its priority is higher than the current -//! // preemption threshold -//! rtfm::set_pending(Interrupt::EXTI2); // ~> exti2 -//! -//! // F -//! rtfm::bkpt(); -//! -//! // Claims can be nested -//! HIGH.claim_mut(t, |_high, _| { -//! // This claim increases the preemption threshold to 3 -//! -//! // Now `exti2` can't preempt this task -//! rtfm::set_pending(Interrupt::EXTI2); -//! -//! // G -//! rtfm::bkpt(); -//! }); -//! -//! // Upon leaving the critical section the preemption threshold drops back -//! // to 2 and `exti2` immediately preempts this task -//! // ~> exti2 -//! }); -//! -//! // Once again the preemption threshold drops but this time to 1. Now the -//! // pending `exti1` task can preempt this task -//! // ~> exti1 -//! } -//! -//! fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) { -//! // C, I -//! rtfm::bkpt(); -//! } -//! -//! fn exti2(_t: &mut Threshold, _r: EXTI2::Resources) { -//! // E, H -//! rtfm::bkpt(); -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_5_late_resources.rs b/src/examples/_5_late_resources.rs deleted file mode 100644 index 8958e85489..0000000000 --- a/src/examples/_5_late_resources.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! Demonstrates initialization of resources in `init`. -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![feature(proc_macro)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use rtfm::{app, Threshold}; -//! -//! app! { -//! device: stm32f103xx, -//! -//! resources: { -//! // Usually, resources are initialized with a constant initializer: -//! static ON: bool = false; -//! -//! // However, there are cases where this is not possible or not desired. -//! // For example, there may not be a sensible value to use, or the type may -//! // not be constructible in a constant (like `Vec`). -//! // -//! // While it is possible to use an `Option` in some cases, that requires -//! // you to properly initialize it and `.unwrap()` it at every use. It -//! // also consumes more memory. -//! // -//! // To solve this, it is possible to defer initialization of resources to -//! // `init` by omitting the initializer. Doing that will require `init` to -//! // return the values of all "late" resources. -//! static IP_ADDRESS: u32; -//! -//! // PORT is used by 2 tasks, making it a shared resource. This just tests -//! // another internal code path and is not important for the example. -//! static PORT: u16; -//! }, -//! -//! idle: { -//! // Test that late resources can be used in idle -//! resources: [IP_ADDRESS], -//! }, -//! -//! tasks: { -//! SYS_TICK: { -//! priority: 1, -//! path: sys_tick, -//! resources: [IP_ADDRESS, PORT, ON], -//! }, -//! -//! EXTI0: { -//! priority: 2, -//! path: exti0, -//! resources: [PORT], -//! } -//! } -//! } -//! -//! // The signature of `init` is now required to have a specific return type. -//! fn init(_p: init::Peripherals, _r: init::Resources) -> init::LateResources { -//! // `init::Resources` does not contain `IP_ADDRESS`, since it is not yet -//! // initialized. -//! //_r.IP_ADDRESS; // doesn't compile -//! -//! // ...obtain value for IP_ADDRESS from EEPROM/DHCP... -//! let ip_address = 0x7f000001; -//! -//! init::LateResources { -//! // This struct will contain fields for all resources with omitted -//! // initializers. -//! IP_ADDRESS: ip_address, -//! PORT: 0, -//! } -//! } -//! -//! fn sys_tick(_t: &mut Threshold, r: SYS_TICK::Resources) { -//! // Other tasks can access late resources like any other, since they are -//! // guaranteed to be initialized when tasks are run. -//! -//! r.IP_ADDRESS; -//! } -//! -//! fn exti0(_t: &mut Threshold, _r: EXTI0::Resources) {} -//! -//! fn idle(_t: &mut Threshold, _r: idle::Resources) -> ! { -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_6_safe_static_mut_ref.rs b/src/examples/_6_safe_static_mut_ref.rs deleted file mode 100644 index 32eb3d98f1..0000000000 --- a/src/examples/_6_safe_static_mut_ref.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! Safe creation of `&'static mut` references -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![feature(proc_macro)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use rtfm::app; -//! -//! app! { -//! device: stm32f103xx, -//! -//! resources: { -//! static BUFFER: [u8; 16] = [0; 16]; -//! }, -//! -//! init: { -//! resources: [BUFFER], -//! }, -//! } -//! -//! fn init(_p: init::Peripherals, r: init::Resources) { -//! let _buf: &'static mut [u8; 16] = r.BUFFER; -//! } -//! -//! fn idle() -> ! { -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_7_generics.rs b/src/examples/_7_generics.rs deleted file mode 100644 index 22bb777af1..0000000000 --- a/src/examples/_7_generics.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Working with resources in a generic fashion -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![feature(proc_macro)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use rtfm::{app, Resource, Threshold}; -//! use stm32f103xx::{SPI1, GPIOA}; -//! -//! app! { -//! device: stm32f103xx, -//! -//! resources: { -//! static GPIOA: GPIOA; -//! static SPI1: SPI1; -//! }, -//! -//! tasks: { -//! EXTI0: { -//! path: exti0, -//! priority: 1, -//! resources: [GPIOA, SPI1], -//! }, -//! -//! EXTI1: { -//! path: exti1, -//! priority: 2, -//! resources: [GPIOA, SPI1], -//! }, -//! }, -//! } -//! -//! fn init(p: init::Peripherals) -> init::LateResources { -//! init::LateResources { -//! GPIOA: p.device.GPIOA, -//! SPI1: p.device.SPI1, -//! } -//! } -//! -//! fn idle() -> ! { -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! // A generic function that uses some resources -//! fn work(t: &mut Threshold, gpioa: &G, spi1: &S) -//! where -//! G: Resource, -//! S: Resource, -//! { -//! gpioa.claim(t, |_gpioa, t| { -//! // drive NSS low -//! -//! spi1.claim(t, |_spi1, _| { -//! // transfer data -//! }); -//! -//! // drive NSS high -//! }); -//! } -//! -//! // This task needs critical sections to access the resources -//! fn exti0(t: &mut Threshold, r: EXTI0::Resources) { -//! work(t, &r.GPIOA, &r.SPI1); -//! } -//! -//! // This task has direct access to the resources -//! fn exti1(t: &mut Threshold, r: EXTI1::Resources) { -//! work(t, &r.GPIOA, &r.SPI1); -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_8_full_syntax.rs b/src/examples/_8_full_syntax.rs deleted file mode 100644 index f8db408753..0000000000 --- a/src/examples/_8_full_syntax.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! A showcase of the `app!` macro syntax -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![feature(proc_macro)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use rtfm::{app, Threshold}; -//! -//! app! { -//! device: stm32f103xx, -//! -//! resources: { -//! static CO_OWNED: u32 = 0; -//! static ON: bool = false; -//! static OWNED: bool = false; -//! static SHARED: bool = false; -//! }, -//! -//! init: { -//! // This is the path to the `init` function -//! // -//! // `init` doesn't necessarily has to be in the root of the crate -//! path: main::init, -//! }, -//! -//! idle: { -//! // This is a path to the `idle` function -//! // -//! // `idle` doesn't necessarily has to be in the root of the crate -//! path: main::idle, -//! resources: [OWNED, SHARED], -//! }, -//! -//! tasks: { -//! SYS_TICK: { -//! path: sys_tick, -//! // If omitted priority is assumed to be 1 -//! // priority: 1, -//! resources: [CO_OWNED, ON, SHARED], -//! }, -//! -//! TIM2: { -//! // Tasks are enabled, between `init` and `idle`, by default but they -//! // can start disabled if `false` is specified here -//! enabled: false, -//! path: tim2, -//! priority: 1, -//! resources: [CO_OWNED], -//! }, -//! }, -//! } -//! -//! mod main { -//! use rtfm::{self, Resource, Threshold}; -//! -//! pub fn init(_p: ::init::Peripherals, _r: ::init::Resources) {} -//! -//! pub fn idle(t: &mut Threshold, mut r: ::idle::Resources) -> ! { -//! loop { -//! *r.OWNED != *r.OWNED; -//! -//! if *r.OWNED { -//! if r.SHARED.claim(t, |shared, _| *shared) { -//! rtfm::wfi(); -//! } -//! } else { -//! r.SHARED.claim_mut(t, |shared, _| *shared = !*shared); -//! } -//! } -//! } -//! } -//! -//! fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { -//! *r.ON = !*r.ON; -//! -//! *r.CO_OWNED += 1; -//! } -//! -//! fn tim2(_t: &mut Threshold, mut r: TIM2::Resources) { -//! *r.CO_OWNED += 1; -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/mod.rs b/src/examples/mod.rs deleted file mode 100644 index 64d1e2ecf4..0000000000 --- a/src/examples/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Examples -// Auto-generated. Do not modify. -pub mod _0_zero_tasks; -pub mod _1_one_task; -pub mod _2_two_tasks; -pub mod _3_preemption; -pub mod _4_nested; -pub mod _5_late_resources; -pub mod _6_safe_static_mut_ref; -pub mod _7_generics; -pub mod _8_full_syntax; diff --git a/src/instant.rs b/src/instant.rs new file mode 100644 index 0000000000..d9a96104b8 --- /dev/null +++ b/src/instant.rs @@ -0,0 +1,60 @@ +use core::cmp::Ordering; +use core::ops; + +use cortex_m::peripheral::DWT; + +#[doc(hidden)] +#[derive(Clone, Copy, Debug)] +pub struct Instant(u32); + +impl Into for Instant { + fn into(self) -> u32 { + self.0 + } +} + +impl Instant { + pub unsafe fn new(timestamp: u32) -> Self { + Instant(timestamp) + } + + pub fn now() -> Self { + Instant(DWT::get_cycle_count()) + } +} + +impl Eq for Instant {} + +impl Ord for Instant { + fn cmp(&self, rhs: &Self) -> Ordering { + (self.0 as i32).wrapping_sub(rhs.0 as i32).cmp(&0) + } +} + +impl PartialEq for Instant { + fn eq(&self, rhs: &Self) -> bool { + self.0.eq(&rhs.0) + } +} + +impl PartialOrd for Instant { + fn partial_cmp(&self, rhs: &Self) -> Option { + Some(self.cmp(rhs)) + } +} + +impl ops::Add for Instant { + type Output = Self; + + fn add(self, rhs: u32) -> Self { + Instant(self.0.wrapping_add(rhs)) + } +} + +impl ops::Sub for Instant { + type Output = i32; + + fn sub(self, rhs: Self) -> i32 { + (self.0 as i32).wrapping_sub(rhs.0 as i32) + } +} diff --git a/src/lib.rs b/src/lib.rs index 8692f28a0f..cb8e0b75b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,177 +1,110 @@ -//! Real Time For the Masses (RTFM) framework 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 M devices are fully supported**. -//! - 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.) -//! -//! # Constraints -//! -//! - Tasks must run to completion. That's it, tasks can't contain endless -//! loops. However, you can run an endless event loop in the `idle` *loop*. -//! -//! - Task priorities must remain constant at runtime. -//! -//! # Dependencies -//! -//! The application crate must depend on a device crate generated using -//! [`svd2rust`] v0.12.x and the "rt" feature of that crate must be enabled. The -//! SVD file used to generate the device crate *must* contain [``] -//! information. -//! -//! [`svd2rust`]: https://docs.rs/svd2rust/0.12.0/svd2rust/ -//! [``]: https://www.keil.com/pack/doc/CMSIS/SVD/html/elem_cpu.html -//! -//! # `app!` -//! -//! The `app!` macro is documented [here]. -//! -//! [here]: https://docs.rs/cortex-m-rtfm-macros/0.3.0/cortex_m_rtfm_macros/fn.app.html -//! -//! # Important: Cortex-M7 devices -//! -//! If targeting a Cortex-M7 device with revision r0p1 then you MUST enable the `cm7-r0p1` Cargo -//! feature of this crate or the `Resource.claim` and `Resource.claim_mut` methods WILL misbehave. -//! -//! # Examples -//! -//! In increasing grade of complexity. See the [examples](./examples/index.html) -//! module. -//! -//! # 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)] +#![allow(warnings)] #![feature(const_fn)] #![feature(proc_macro)] #![feature(untagged_unions)] -#![feature(unsize)] +#![feature(never_type)] #![no_std] extern crate cortex_m; extern crate cortex_m_rtfm_macros; extern crate heapless; -extern crate rtfm_core; -extern crate untagged_option; +extern crate typenum; -use core::{mem, u8}; +mod instant; +mod node; +mod resource; +mod tq; -pub use cortex_m::asm::{bkpt, wfi}; +use core::mem; + +#[doc(hidden)] +pub use cortex_m::interrupt; +use cortex_m::interrupt::Nr; +#[doc(hidden)] +pub use cortex_m::peripheral::syst::SystClkSource; +#[cfg(any(has_fpu, target_arch = "x86_64"))] +use cortex_m::peripheral::FPU; +use cortex_m::peripheral::{Peripherals, CPUID, DCB, DWT, MPU, NVIC, SCB, SYST}; +#[cfg(any(armv7m, target_arch = "x86_64"))] +use cortex_m::peripheral::{CBP, FPB, ITM, TPIU}; pub use cortex_m_rtfm_macros::app; -pub use rtfm_core::{Resource, Threshold}; -#[doc(hidden)] -pub use untagged_option::UntaggedOption; +use heapless::ring_buffer::RingBuffer; +pub use typenum::consts::*; +pub use typenum::{Max, Maximum, Unsigned}; -use cortex_m::interrupt::{self, Nr}; -use cortex_m::peripheral::NVIC; -#[cfg(not(armv6m))] -use cortex_m::register::basepri; +pub use instant::Instant; +pub use node::Node; +use node::{Slot, TaggedPayload}; +pub use resource::{Resource, Threshold}; +pub use tq::{dispatch, TimerQueue}; -pub mod examples; -#[doc(hidden)] -pub mod ll; +pub type PayloadQueue = RingBuffer, N, u8>; +pub type SlotQueue = RingBuffer, N, u8>; +pub type Ceiling = ::Ceiling; -/// Executes the closure `f` in a preemption free context -/// -/// During the execution of the closure no task can preempt the current task. -pub fn atomic(t: &mut Threshold, f: F) -> R -where - F: FnOnce(&mut Threshold) -> R, -{ - if t.value() == u8::MAX { - f(t) - } else { - interrupt::disable(); - let r = f(&mut unsafe { Threshold::max() }); - unsafe { interrupt::enable() }; - r +pub struct Core { + #[cfg(any(armv7m, target_arch = "x86_64"))] + pub CBP: CBP, + pub CPUID: CPUID, + pub DCB: DCB, + // pub DWT: DWT, + #[cfg(any(armv7m, target_arch = "x86_64"))] + pub FPB: FPB, + #[cfg(any(has_fpu, target_arch = "x86_64"))] + pub FPU: FPU, + #[cfg(any(armv7m, target_arch = "x86_64"))] + pub ITM: ITM, + pub MPU: MPU, + pub SCB: SCB, + // pub SYST: SYST, + #[cfg(any(armv7m, target_arch = "x86_64"))] + pub TPIU: TPIU, +} + +impl Core { + pub unsafe fn steal() -> (Core, DWT, NVIC, SYST) { + let p = Peripherals::steal(); + + ( + Core { + #[cfg(any(armv7m, target_arch = "x86_64"))] + CBP: p.CBP, + CPUID: p.CPUID, + DCB: p.DCB, + #[cfg(any(armv7m, target_arch = "x86_64"))] + FPB: p.FPB, + #[cfg(any(has_fpu, target_arch = "x86_64"))] + FPU: p.FPU, + #[cfg(any(armv7m, target_arch = "x86_64"))] + ITM: p.ITM, + MPU: p.MPU, + SCB: p.SCB, + #[cfg(any(armv7m, target_arch = "x86_64"))] + TPIU: p.TPIU, + }, + p.DWT, + p.NVIC, + p.SYST, + ) } } -#[inline] #[doc(hidden)] -pub unsafe fn claim( - data: T, - ceiling: u8, - _nvic_prio_bits: u8, - t: &mut Threshold, - f: F, -) -> R -where - F: FnOnce(T, &mut Threshold) -> R, -{ - if ceiling > t.value() { - match () { - #[cfg(armv6m)] - () => atomic(t, |t| f(data, t)), - - #[cfg(not(armv6m))] - () => { - let max_priority = 1 << _nvic_prio_bits; - - if ceiling == max_priority { - atomic(t, |t| f(data, t)) - } else { - let old = basepri::read(); - let hw = (max_priority - ceiling) << (8 - _nvic_prio_bits); - basepri::write(hw); - let ret = f(data, &mut Threshold::new(ceiling)); - basepri::write(old); - ret - } - } - } - } else { - f(data, t) +pub const unsafe fn uninitialized() -> T { + #[allow(unions_with_drop_fields)] + union U { + some: T, + none: (), } + + U { none: () }.some } -/// Sets an interrupt, that is a task, as pending -/// -/// If the task priority is high enough the task will be serviced immediately, -/// otherwise it will be serviced at some point after the current task ends. -pub fn set_pending(interrupt: I) +pub unsafe fn set_pending(interrupt: I) where I: Nr, { - // NOTE(safe) atomic write - let mut nvic: NVIC = unsafe { mem::transmute(()) }; - nvic.set_pending(interrupt); + mem::transmute::<(), NVIC>(()).set_pending(interrupt) } diff --git a/src/ll.rs b/src/node.rs similarity index 52% rename from src/ll.rs rename to src/node.rs index 85d0bfc779..9fe1bff283 100644 --- a/src/ll.rs +++ b/src/node.rs @@ -1,19 +1,15 @@ use core::cmp::Ordering; -use core::marker::Unsize; -use core::ops; use core::{mem, ptr}; -use cortex_m::peripheral::{DWT, SYST}; -use heapless::binary_heap::{BinaryHeap, Min}; -pub use heapless::ring_buffer::{Consumer, Producer, RingBuffer}; +use instant::Instant; +#[doc(hidden)] #[repr(C)] pub struct Node where T: 'static, { baseline: Instant, - next: Option>, payload: T, } @@ -37,6 +33,7 @@ impl PartialOrd for Node { } } +#[doc(hidden)] pub struct Slot where T: 'static, @@ -45,10 +42,6 @@ where } impl Slot { - pub fn new(node: &'static mut Node) -> Self { - Slot { node } - } - pub fn write(self, bl: Instant, data: T) -> Payload { self.node.baseline = bl; unsafe { ptr::write(&mut self.node.payload, data) } @@ -56,31 +49,13 @@ impl Slot { } } -pub struct FreeList -where - T: 'static, -{ - head: Option>, -} - -impl FreeList { - pub const fn new() -> Self { - FreeList { head: None } - } - - pub fn pop(&mut self) -> Option> { - self.head.take().map(|head| { - self.head = head.node.next.take(); - head - }) - } - - pub fn push(&mut self, slot: Slot) { - slot.node.next = self.head.take(); - self.head = Some(slot); +impl Into> for &'static mut Node { + fn into(self) -> Slot { + Slot { node: self } } } +#[doc(hidden)] pub struct Payload where T: 'static, @@ -105,6 +80,7 @@ impl Payload { } } +#[doc(hidden)] pub struct TaggedPayload where A: Copy, @@ -172,80 +148,3 @@ where Some(self.cmp(rhs)) } } - -pub struct TimerQueue -where - A: Unsize<[TaggedPayload]>, - T: Copy, -{ - pub syst: SYST, - pub queue: BinaryHeap, A, Min>, -} - -impl TimerQueue -where - A: Unsize<[TaggedPayload]>, - T: Copy, -{ - pub const fn new(syst: SYST) -> Self { - TimerQueue { - syst, - queue: BinaryHeap::new(), - } - } -} - -#[derive(Clone, Copy, Debug)] -pub struct Instant(u32); - -impl Instant { - pub fn now() -> Self { - Instant(DWT::get_cycle_count()) - } -} - -impl Eq for Instant {} - -impl Ord for Instant { - fn cmp(&self, rhs: &Self) -> Ordering { - (self.0 as i32).wrapping_sub(rhs.0 as i32).cmp(&0) - } -} - -impl PartialEq for Instant { - fn eq(&self, rhs: &Self) -> bool { - self.0.eq(&rhs.0) - } -} - -impl PartialOrd for Instant { - fn partial_cmp(&self, rhs: &Self) -> Option { - Some(self.cmp(rhs)) - } -} - -impl ops::Add for Instant { - type Output = Self; - - fn add(self, rhs: u32) -> Self { - Instant(self.0.wrapping_add(rhs)) - } -} - -impl ops::Sub for Instant { - type Output = i32; - - fn sub(self, rhs: Self) -> i32 { - (self.0 as i32).wrapping_sub(rhs.0 as i32) - } -} - -pub const unsafe fn uninitialized() -> T { - #[allow(unions_with_drop_fields)] - union U { - some: T, - none: (), - } - - U { none: () }.some -} diff --git a/src/resource.rs b/src/resource.rs new file mode 100644 index 0000000000..f3f17d3aac --- /dev/null +++ b/src/resource.rs @@ -0,0 +1,88 @@ +use core::marker::PhantomData; + +#[cfg(not(armv6m))] +use cortex_m::register::basepri; + +use typenum::{Max, Maximum, Unsigned}; + +pub struct Threshold +where + N: Unsigned, +{ + _not_send_or_sync: PhantomData<*const ()>, + _n: PhantomData, +} + +impl Threshold +where + N: Unsigned, +{ + pub unsafe fn new() -> Self { + Threshold { + _not_send_or_sync: PhantomData, + _n: PhantomData, + } + } +} + +pub unsafe trait Resource { + #[doc(hidden)] + const NVIC_PRIO_BITS: u8; + type Ceiling: Unsigned; + type Data: 'static + Send; + + #[doc(hidden)] + unsafe fn get() -> &'static mut Self::Data; + + fn borrow<'cs>(&'cs self, _t: &'cs Threshold) -> &'cs Self::Data { + unsafe { Self::get() } + } + + fn borrow_mut<'cs>(&'cs mut self, _t: &'cs Threshold) -> &'cs mut Self::Data { + unsafe { Self::get() } + } + + fn claim<'cs, R, F, P>(&self, _t: &mut Threshold

, f: F) -> R + where + F: FnOnce(&Self::Data, &mut Threshold>) -> R, + P: Max + Unsigned, + Maximum: Unsigned, + { + unsafe { + if P::to_u8() >= Self::Ceiling::to_u8() { + f(Self::get(), &mut Threshold::new()) + } else { + let max = 1 << Self::NVIC_PRIO_BITS; + let new = (max - Self::Ceiling::to_u8()) << (8 - Self::NVIC_PRIO_BITS); + + let old = basepri::read(); + basepri::write(new); + let r = f(Self::get(), &mut Threshold::new()); + basepri::write(old); + r + } + } + } + + fn claim_mut<'cs, R, F, P>(&mut self, _t: &mut Threshold

, f: F) -> R + where + F: FnOnce(&mut Self::Data, &mut Threshold>) -> R, + P: Max + Unsigned, + Maximum: Unsigned, + { + unsafe { + if P::to_u8() >= Self::Ceiling::to_u8() { + f(Self::get(), &mut Threshold::new()) + } else { + let max = 1 << Self::NVIC_PRIO_BITS; + let new = (max - Self::Ceiling::to_u8()) << (8 - Self::NVIC_PRIO_BITS); + + let old = basepri::read(); + basepri::write(new); + let r = f(Self::get(), &mut Threshold::new()); + basepri::write(old); + r + } + } + } +} diff --git a/src/tq.rs b/src/tq.rs new file mode 100644 index 0000000000..3bdb41ceb4 --- /dev/null +++ b/src/tq.rs @@ -0,0 +1,109 @@ +use core::cmp; + +use cortex_m::peripheral::{SCB, SYST}; +use heapless::binary_heap::{BinaryHeap, Min}; +use heapless::ArrayLength; +use typenum::{Max, Maximum, Unsigned}; + +use instant::Instant; +use node::{Slot, TaggedPayload}; +use resource::{Resource, Threshold}; + +enum State +where + T: Copy, +{ + Payload(TaggedPayload), + Baseline(Instant), + Done, +} + +#[doc(hidden)] +pub struct TimerQueue +where + N: ArrayLength>, + T: Copy, +{ + pub syst: SYST, + pub queue: BinaryHeap, N, Min>, +} + +impl TimerQueue +where + N: ArrayLength>, + T: Copy, +{ + pub const fn new(syst: SYST) -> Self { + TimerQueue { + syst, + queue: BinaryHeap::new(), + } + } + + #[inline] + pub unsafe fn enqueue(&mut self, bl: Instant, tp: TaggedPayload) { + if self.queue + .peek() + .map(|head| bl < head.baseline()) + .unwrap_or(true) + { + self.syst.enable_interrupt(); + // set SysTick pending + unsafe { (*SCB::ptr()).icsr.write(1 << 26) } + } + + self.queue.push_unchecked(tp); + } +} + +pub fn dispatch(t: &mut Threshold

, tq: &mut TQ, mut f: F) +where + F: FnMut(&mut Threshold

, TaggedPayload), + Maximum: Unsigned, + N: 'static + ArrayLength>, + P: Unsigned + Max, + T: 'static + Copy + Send, + TQ: Resource>, +{ + loop { + let state = tq.claim_mut(t, |tq, _| { + if let Some(bl) = tq.queue.peek().map(|p| p.baseline()) { + if Instant::now() >= bl { + // message ready + State::Payload(unsafe { tq.queue.pop_unchecked() }) + } else { + // set a new timeout + State::Baseline(bl) + } + } else { + // empty queue + tq.syst.disable_interrupt(); + State::Done + } + }); + + match state { + State::Payload(p) => f(t, p), + State::Baseline(bl) => { + const MAX: u32 = 0x00ffffff; + + let diff = bl - Instant::now(); + + if diff < 0 { + // message became ready + continue; + } else { + tq.claim_mut(t, |tq, _| { + tq.syst.set_reload(cmp::min(MAX, diff as u32)); + // start counting from the new reload + tq.syst.clear_current(); + }); + return; + } + } + State::Done => { + return; + } + } + } +}