diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/examples/_0_zero_tasks.rs | 47 | ||||
-rw-r--r-- | src/examples/_1_one_task.rs | 100 | ||||
-rw-r--r-- | src/examples/_2_two_tasks.rs | 62 | ||||
-rw-r--r-- | src/examples/_3_preemption.rs | 71 | ||||
-rw-r--r-- | src/examples/_4_nested.rs | 132 | ||||
-rw-r--r-- | src/examples/_5_late_resources.rs | 90 | ||||
-rw-r--r-- | src/examples/_6_safe_static_mut_ref.rs | 35 | ||||
-rw-r--r-- | src/examples/_7_generics.rs | 77 | ||||
-rw-r--r-- | src/examples/_8_full_syntax.rs | 87 | ||||
-rw-r--r-- | src/examples/mod.rs | 11 | ||||
-rw-r--r-- | src/export.rs | 84 | ||||
-rw-r--r-- | src/lib.rs | 436 | ||||
-rw-r--r-- | src/tq.rs | 135 |
13 files changed, 523 insertions, 844 deletions
diff --git a/src/examples/_0_zero_tasks.rs b/src/examples/_0_zero_tasks.rs deleted file mode 100644 index 0484bb9d..00000000 --- a/src/examples/_0_zero_tasks.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Minimal example with zero tasks -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![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 b9075a59..00000000 --- a/src/examples/_1_one_task.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! An application with one task -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![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 516ff0c9..00000000 --- a/src/examples/_2_two_tasks.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Two tasks running at the *same* priority with access to the same resource -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![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 14c9d925..00000000 --- a/src/examples/_3_preemption.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! Two tasks running at *different* priorities with access to the same resource -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![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 26f8fd84..00000000 --- a/src/examples/_4_nested.rs +++ /dev/null @@ -1,132 +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)] -//! #![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 { -//! LOW: mut low, -//! HIGH: 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 7ab90a4e..00000000 --- a/src/examples/_5_late_resources.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! Demonstrates initialization of resources in `init`. -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![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 8f7267f5..00000000 --- a/src/examples/_6_safe_static_mut_ref.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Safe creation of `&'static mut` references -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![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 5dafdbf2..00000000 --- a/src/examples/_7_generics.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! Working with resources in a generic fashion -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use rtfm::{app, Resource, Threshold}; -//! use stm32f103xx::{GPIOA, SPI1}; -//! -//! 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<G, S>(t: &mut Threshold, gpioa: &G, spi1: &S) -//! where -//! G: Resource<Data = GPIOA>, -//! S: Resource<Data = SPI1>, -//! { -//! 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 cc7fbc22..00000000 --- a/src/examples/_8_full_syntax.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! A showcase of the `app!` macro syntax -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![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 64d1e2ec..00000000 --- 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/export.rs b/src/export.rs new file mode 100644 index 00000000..cb63e0ce --- /dev/null +++ b/src/export.rs @@ -0,0 +1,84 @@ +/// IMPLEMENTATION DETAILS. DO NOT USE ANYTHING IN THIS MODULE +use core::{hint, ptr}; + +#[cfg(armv7m)] +use cortex_m::register::basepri; +pub use cortex_m::{ + asm::wfi, interrupt, peripheral::scb::SystemHandler, peripheral::syst::SystClkSource, + peripheral::Peripherals, +}; +pub use cortex_m_rt::{entry, exception}; +pub use heapless::consts; +use heapless::spsc::Queue; + +#[cfg(feature = "timer-queue")] +pub use crate::tq::{isr as sys_tick, NotReady, TimerQueue}; + +pub type FreeQueue<N> = Queue<u8, N>; +pub type ReadyQueue<T, N> = Queue<(T, u8), N>; + +#[cfg(armv7m)] +#[inline(always)] +pub fn run<F>(f: F) +where + F: FnOnce(), +{ + let initial = basepri::read(); + f(); + unsafe { basepri::write(initial) } +} + +#[cfg(not(armv7m))] +#[inline(always)] +pub fn run<F>(f: F) +where + F: FnOnce(), +{ + f(); +} + +// TODO(MaybeUninit) Until core::mem::MaybeUninit is stabilized we use our own (inefficient) +// implementation +pub struct MaybeUninit<T> { + value: Option<T>, +} + +impl<T> MaybeUninit<T> { + pub const fn uninitialized() -> Self { + MaybeUninit { value: None } + } + + pub unsafe fn get_ref(&self) -> &T { + if let Some(x) = self.value.as_ref() { + x + } else { + hint::unreachable_unchecked() + } + } + + pub unsafe fn get_mut(&mut self) -> &mut T { + if let Some(x) = self.value.as_mut() { + x + } else { + hint::unreachable_unchecked() + } + } + + pub fn set(&mut self, val: T) { + unsafe { ptr::write(&mut self.value, Some(val)) } + } +} + +#[inline(always)] +pub fn assert_send<T>() +where + T: Send, +{ +} + +#[inline(always)] +pub fn assert_sync<T>() +where + T: Sync, +{ +} @@ -1,170 +1,342 @@ //! 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). +//! **IMPORTANT**: This crate is published as [`cortex-m-rtfm`] on crates.io but the name of the +//! library is `rtfm`. //! -//! [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 +//! [`cortex-m-rtfm`]: https://crates.io/crates/cortex-m-rtfm //! -//! # Features +//! The user level documentation can be found [here]. //! -//! - **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.) +//! [here]: ../../book/index.html //! -//! # Constraints +//! Don't forget to check the documentation of the [`#[app]`] attribute, which is the main component +//! of the framework. //! -//! - 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*. +//! [`#[app]`]: ../cortex_m_rtfm_macros/attr.app.html //! -//! - Task priorities must remain constant at runtime. +//! # Cargo features //! -//! # Dependencies +//! - `timer-queue`. This opt-in feature enables the `schedule` API which can be used to schedule +//! tasks to run in the future. Also see [`Instant`] and [`Duration`]. //! -//! 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 [`<cpu>`] -//! information. -//! -//! [`svd2rust`]: https://docs.rs/svd2rust/0.12.0/svd2rust/ -//! [`<cpu>`]: 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 +//! [`Instant`]: struct.Instant.html +//! [`Duration`]: struct.Duration.html + #![deny(missing_docs)] #![deny(warnings)] #![no_std] -extern crate cortex_m; -extern crate cortex_m_rtfm_macros; -extern crate rtfm_core; -extern crate untagged_option; +use core::{cell::Cell, u8}; +#[cfg(feature = "timer-queue")] +use core::{cmp::Ordering, ops}; -use core::{mem, u8}; - -pub use cortex_m::asm::{bkpt, wfi}; +#[cfg(not(feature = "timer-queue"))] +use cortex_m::peripheral::SYST; +#[cfg(armv7m)] +use cortex_m::register::basepri; +use cortex_m::{ + interrupt::{self, Nr}, + peripheral::{CBP, CPUID, DCB, DWT, FPB, FPU, ITM, MPU, NVIC, SCB, TPIU}, +}; pub use cortex_m_rtfm_macros::app; -pub use rtfm_core::{Resource, Threshold}; + +#[doc(hidden)] +pub mod export; #[doc(hidden)] -pub use untagged_option::UntaggedOption; +#[cfg(feature = "timer-queue")] +mod tq; -use cortex_m::interrupt::{self, Nr}; -use cortex_m::peripheral::NVIC; -#[cfg(not(armv6m))] -use cortex_m::register::basepri; +/// Core peripherals +/// +/// This is `cortex_m::Peripherals` minus the peripherals that the RTFM runtime uses +/// +/// - The `NVIC` field is never present. +/// - When the `timer-queue` feature is enabled the following fields are *not* present: `DWT` and +/// `SYST`. +#[allow(non_snake_case)] +pub struct Peripherals<'a> { + /// Cache and branch predictor maintenance operations (not present on Cortex-M0 variants) + pub CBP: CBP, + + /// CPUID + pub CPUID: CPUID, + + /// Debug Control Block (by value if the `timer-queue` feature is disabled) + #[cfg(feature = "timer-queue")] + pub DCB: &'a mut DCB, + + /// Debug Control Block (borrowed if the `timer-queue` feature is enabled) + #[cfg(not(feature = "timer-queue"))] + pub DCB: DCB, + + /// Data Watchpoint and Trace unit (not present if the `timer-queue` feature is enabled) + #[cfg(not(feature = "timer-queue"))] + pub DWT: DWT, -pub mod examples; + /// Flash Patch and Breakpoint unit (not present on Cortex-M0 variants) + pub FPB: FPB, -/// Executes the closure `f` in a preemption free context + /// Floating Point Unit (only present on `thumbv7em-none-eabihf`) + pub FPU: FPU, + + /// Instrumentation Trace Macrocell (not present on Cortex-M0 variants) + pub ITM: ITM, + + /// Memory Protection Unit + pub MPU: MPU, + + // Nested Vector Interrupt Controller + // pub NVIC: NVIC, + /// System Control Block + pub SCB: &'a mut SCB, + + /// SysTick: System Timer (not present if the `timer-queue` is enabled) + #[cfg(not(feature = "timer-queue"))] + pub SYST: SYST, + + /// Trace Port Interface Unit (not present on Cortex-M0 variants) + pub TPIU: TPIU, +} + +/// A measurement of a monotonically nondecreasing clock. Opaque and useful only with `Duration` /// -/// During the execution of the closure no task can preempt the current task. -pub fn atomic<R, F>(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 +/// This data type is only available when the `timer-queue` feature is enabled +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[cfg(feature = "timer-queue")] +pub struct Instant(i32); + +#[cfg(feature = "timer-queue")] +impl Instant { + /// IMPLEMENTATION DETAIL. DO NOT USE + #[doc(hidden)] + pub fn artificial(timestamp: i32) -> Self { + Instant(timestamp) + } + + /// Returns an instant corresponding to "now" + pub fn now() -> Self { + Instant(DWT::get_cycle_count() as i32) + } + + /// Returns the amount of time elapsed since this instant was created. + pub fn elapsed(&self) -> Duration { + Instant::now() - *self + } + + /// Returns the amount of time elapsed from another instant to this one. + pub fn duration_since(&self, earlier: Instant) -> Duration { + let diff = self.0 - earlier.0; + assert!(diff >= 0, "second instant is later than self"); + Duration(diff as u32) } } -#[inline] -#[doc(hidden)] -pub unsafe fn claim<T, R, F>( - 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(feature = "timer-queue")] +impl ops::AddAssign<Duration> for Instant { + fn add_assign(&mut self, dur: Duration) { + debug_assert!(dur.0 < (1 << 31)); + self.0 = self.0.wrapping_add(dur.0 as i32); + } +} + +#[cfg(feature = "timer-queue")] +impl ops::Add<Duration> for Instant { + type Output = Self; - #[cfg(not(armv6m))] - () => { - let max_priority = 1 << _nvic_prio_bits; + fn add(mut self, dur: Duration) -> Self { + self += dur; + self + } +} - if ceiling == max_priority { - atomic(t, |t| f(data, t)) +#[cfg(feature = "timer-queue")] +impl ops::SubAssign<Duration> for Instant { + fn sub_assign(&mut self, dur: Duration) { + // XXX should this be a non-debug assertion? + debug_assert!(dur.0 < (1 << 31)); + self.0 = self.0.wrapping_sub(dur.0 as i32); + } +} + +#[cfg(feature = "timer-queue")] +impl ops::Sub<Duration> for Instant { + type Output = Self; + + fn sub(mut self, dur: Duration) -> Self { + self -= dur; + self + } +} + +#[cfg(feature = "timer-queue")] +impl ops::Sub<Instant> for Instant { + type Output = Duration; + + fn sub(self, other: Instant) -> Duration { + self.duration_since(other) + } +} + +#[cfg(feature = "timer-queue")] +impl Ord for Instant { + fn cmp(&self, rhs: &Self) -> Ordering { + self.0.wrapping_sub(rhs.0).cmp(&0) + } +} + +#[cfg(feature = "timer-queue")] +impl PartialOrd for Instant { + fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> { + Some(self.cmp(rhs)) + } +} + +/// A `Duration` type to represent a span of time. +/// +/// This data type is only available when the `timer-queue` feature is enabled +#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] +#[cfg(feature = "timer-queue")] +pub struct Duration(u32); + +#[cfg(feature = "timer-queue")] +impl ops::AddAssign for Duration { + fn add_assign(&mut self, dur: Duration) { + self.0 += dur.0; + } +} + +#[cfg(feature = "timer-queue")] +impl ops::Add<Duration> for Duration { + type Output = Self; + + fn add(self, other: Self) -> Self { + Duration(self.0 + other.0) + } +} + +#[cfg(feature = "timer-queue")] +impl ops::SubAssign for Duration { + fn sub_assign(&mut self, rhs: Duration) { + self.0 -= rhs.0; + } +} + +#[cfg(feature = "timer-queue")] +impl ops::Sub<Duration> for Duration { + type Output = Self; + + fn sub(self, rhs: Self) -> Self { + Duration(self.0 - rhs.0) + } +} + +/// Adds the `cycles` method to the `u32` type +/// +/// This trait is only available when the `timer-queue` feature is enabled +#[cfg(feature = "timer-queue")] +pub trait U32Ext { + /// Converts the `u32` value into clock cycles + fn cycles(self) -> Duration; +} + +#[cfg(feature = "timer-queue")] +impl U32Ext for u32 { + fn cycles(self) -> Duration { + Duration(self) + } +} + +/// Memory safe access to shared resources +/// +/// In RTFM, locks are implemented as critical sections that prevent other tasks from *starting*. +/// These critical sections are implemented by temporarily increasing the dynamic priority (see +/// [BASEPRI]) of the current context. +/// +/// [BASEPRI]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100701/latest/special-purpose-mask-registers +pub unsafe trait Mutex { + /// IMPLEMENTATION DETAIL. DO NOT USE THIS CONSTANT + #[doc(hidden)] + const CEILING: u8; + + /// IMPLEMENTATION DETAIL. DO NOT USE THIS CONSTANT + #[doc(hidden)] + const NVIC_PRIO_BITS: u8; + + /// Data protected by the mutex + type Data: Send; + + /// IMPLEMENTATION DETAIL. DO NOT USE THIS METHOD + #[doc(hidden)] + unsafe fn priority(&self) -> &Cell<u8>; + + /// IMPLEMENTATION DETAIL. DO NOT USE THIS METHOD + #[doc(hidden)] + fn ptr(&self) -> *mut Self::Data; + + /// Creates a critical section and grants temporary access to the protected data + #[inline(always)] + #[cfg(armv7m)] + fn lock<R, F>(&mut self, f: F) -> R + where + F: FnOnce(&mut Self::Data) -> R, + { + unsafe { + let current = self.priority().get(); + + if self.priority().get() < Self::CEILING { + if Self::CEILING == (1 << Self::NVIC_PRIO_BITS) { + self.priority().set(u8::MAX); + let r = interrupt::free(|_| f(&mut *self.ptr())); + self.priority().set(current); + r } 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 + self.priority().set(Self::CEILING); + basepri::write(logical2hw(Self::CEILING, Self::NVIC_PRIO_BITS)); + let r = f(&mut *self.ptr()); + basepri::write(logical2hw(current, Self::NVIC_PRIO_BITS)); + self.priority().set(current); + r } + } else { + f(&mut *self.ptr()) } } - } else { - f(data, t) } + + /// Creates a critical section and grants temporary access to the protected data + #[cfg(not(armv7m))] + fn lock<R, F>(&mut self, f: F) -> R + where + F: FnOnce(&mut Self::Data) -> R, + { + unsafe { + let current = self.priority().get(); + + if self.priority().get() < Self::CEILING { + self.priority().set(u8::MAX); + let r = interrupt::free(|_| f(&mut *self.ptr())); + self.priority().set(current); + r + } else { + f(&mut *self.ptr()) + } + } + } +} + +#[cfg(armv7m)] +#[inline] +fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { + ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) } -/// Sets an interrupt, that is a task, as pending +/// Sets the given `interrupt` 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<I>(interrupt: I) +/// This is a convenience function around +/// [`NVIC::pend`](../cortex_m/peripheral/struct.NVIC.html#method.pend) +pub fn pend<I>(interrupt: I) where I: Nr, { - // NOTE(safe) atomic write - let mut nvic: NVIC = unsafe { mem::transmute(()) }; - nvic.set_pending(interrupt); + NVIC::pend(interrupt) } diff --git a/src/tq.rs b/src/tq.rs new file mode 100644 index 00000000..88db2f84 --- /dev/null +++ b/src/tq.rs @@ -0,0 +1,135 @@ +use core::cmp::{self, Ordering}; + +use cortex_m::peripheral::{SCB, SYST}; +use heapless::{binary_heap::Min, ArrayLength, BinaryHeap}; + +use crate::{Instant, Mutex}; + +pub struct TimerQueue<T, N> +where + N: ArrayLength<NotReady<T>>, + T: Copy, +{ + pub syst: SYST, + pub queue: BinaryHeap<NotReady<T>, N, Min>, +} + +impl<T, N> TimerQueue<T, N> +where + N: ArrayLength<NotReady<T>>, + T: Copy, +{ + pub fn new(syst: SYST) -> Self { + TimerQueue { + syst, + queue: BinaryHeap::new(), + } + } + + #[inline] + pub unsafe fn enqueue_unchecked(&mut self, nr: NotReady<T>) { + let mut is_empty = true; + if self + .queue + .peek() + .map(|head| { + is_empty = false; + nr.instant < head.instant + }) + .unwrap_or(true) + { + if is_empty { + self.syst.enable_interrupt(); + } + + // set SysTick pending + (*SCB::ptr()).icsr.write(1 << 26); + } + + self.queue.push_unchecked(nr); + } +} + +pub struct NotReady<T> +where + T: Copy, +{ + pub index: u8, + pub instant: Instant, + pub task: T, +} + +impl<T> Eq for NotReady<T> where T: Copy {} + +impl<T> Ord for NotReady<T> +where + T: Copy, +{ + fn cmp(&self, other: &Self) -> Ordering { + self.instant.cmp(&other.instant) + } +} + +impl<T> PartialEq for NotReady<T> +where + T: Copy, +{ + fn eq(&self, other: &Self) -> bool { + self.instant == other.instant + } +} + +impl<T> PartialOrd for NotReady<T> +where + T: Copy, +{ + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + Some(self.cmp(&other)) + } +} + +#[inline(always)] +pub fn isr<TQ, T, N, F>(mut tq: TQ, mut f: F) +where + TQ: Mutex<Data = TimerQueue<T, N>>, + T: Copy + Send, + N: ArrayLength<NotReady<T>>, + F: FnMut(T, u8), +{ + loop { + // XXX does `#[inline(always)]` improve performance or not? + let next = tq.lock(#[inline(always)] + |tq| { + if let Some(instant) = tq.queue.peek().map(|p| p.instant) { + let diff = instant.0.wrapping_sub(Instant::now().0); + + if diff < 0 { + // task became ready + let m = unsafe { tq.queue.pop_unchecked() }; + + Some((m.task, m.index)) + } else { + // set a new timeout + const MAX: u32 = 0x00ffffff; + + tq.syst.set_reload(cmp::min(MAX, diff as u32)); + + // start counting down from the new reload + tq.syst.clear_current(); + + None + } + } else { + // the queue is empty + tq.syst.disable_interrupt(); + None + } + }); + + if let Some((task, index)) = next { + f(task, index) + } else { + return; + } + } +} |