diff options
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 288 |
1 files changed, 156 insertions, 132 deletions
@@ -1,3 +1,57 @@ +//! Real Time For the Masses (RTFM), a framework for building concurrent +//! applications, for ARM Cortex-M microcontrollers +//! +//! This crate is based on [the RTFM framework] created by the Embedded Systems +//! group at [LuleƄ University of Technology][ltu], led by Prof. Per Lindgren, +//! and uses a simplified version of the Stack Resource Policy as scheduling +//! policy (check the [references] for details). +//! +//! [the RTFM framework]: http://www.rtfm-lang.org/ +//! [ltu]: https://www.ltu.se/?l=en +//! [per]: https://www.ltu.se/staff/p/pln-1.11258?l=en +//! [references]: ./index.html#references +//! +//! # Features +//! +//! - **Event triggered tasks** as the unit of concurrency. +//! - Support for prioritization of tasks and, thus, **preemptive +//! multitasking**. +//! - **Efficient and data race free memory sharing** through fine grained *non +//! global* critical sections. +//! - **Deadlock free execution** guaranteed at compile time. +//! - **Minimal scheduling overhead** as the scheduler has no "software +//! component": the hardware does all the scheduling. +//! - **Highly efficient memory usage**: All the tasks share a single call stack +//! and there's no hard dependency on a dynamic memory allocator. +//! - **All Cortex 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` function. +//! +//! - Task priorities must remain constant at runtime. +//! +//! # Dependencies +//! +//! - A device crate generated using [`svd2rust`] v0.11.x. The input SVD file +//! *must* contain [`<cpu>`] information. +//! - A `start` lang time: Vanilla `main` must be supported in binary crates. +//! You can use the [`cortex-m-rt`] crate to fulfill the requirement +//! +//! [`svd2rust`]: https://docs.rs/svd2rust/0..0/svd2rust/ +//! [`<cpu>`]: https://www.keil.com/pack/doc/CMSIS/SVD/html/elem_cpu.html +//! [`cortex-m-rt`]: https://docs.rs/cortex-m-rt/0.3.0/cortex_m_rt/ +//! +//! # Examples +//! +//! In increasing grade of complexity: [examples](./examples/index.html) + +#![deny(missing_docs)] +#![deny(warnings)] #![feature(asm)] #![feature(const_fn)] #![feature(optin_builtin_traits)] @@ -17,11 +71,73 @@ pub use cortex_m::interrupt::free as atomic; pub use static_ref::Static; use cortex_m::interrupt::Nr; #[cfg(not(armv6m))] -use cortex_m::register::{basepri_max, basepri}; +use cortex_m::register::{basepri, basepri_max}; + +pub mod examples; + +/// A resource, a means to share data between tasks +pub trait Resource { + /// The data protected by the resource + type Data; + + /// Borrows the resource data for the duration of a *global* critical + /// section + fn borrow<'cs>( + &'cs self, + cs: &'cs CriticalSection, + ) -> &'cs Static<Self::Data>; + + /// Mutable variant of `borrow` + fn borrow_mut<'cs>( + &'cs mut self, + cs: &'cs CriticalSection, + ) -> &'cs mut Static<Self::Data>; + + /// Claims the resource data for the span of the closure `f`. For the + /// duration of the closure other tasks that may access the resource data + /// are prevented from preempting the current task. + fn claim<R, F>(&self, t: &mut Threshold, f: F) -> R + where + F: FnOnce(&Static<Self::Data>, &mut Threshold) -> R; + + /// Mutable variant of `claim` + fn claim_mut<R, F>(&mut self, t: &mut Threshold, f: F) -> R + where + F: FnOnce(&mut Static<Self::Data>, &mut Threshold) -> R; +} + +impl<T> Resource for Static<T> { + type Data = T; + + fn borrow<'cs>(&'cs self, _cs: &'cs CriticalSection) -> &'cs Static<T> { + self + } + + fn borrow_mut<'cs>( + &'cs mut self, + _cs: &'cs CriticalSection, + ) -> &'cs mut Static<T> { + self + } -#[inline(always)] -unsafe fn claim<T, U, R, F, G>( - data: T, + fn claim<R, F>(&self, t: &mut Threshold, f: F) -> R + where + F: FnOnce(&Static<Self::Data>, &mut Threshold) -> R, + { + f(self, t) + } + + fn claim_mut<R, F>(&mut self, t: &mut Threshold, f: F) -> R + where + F: FnOnce(&mut Static<Self::Data>, &mut Threshold) -> R, + { + f(self, t) + } +} + +#[doc(hidden)] +pub unsafe fn claim<T, U, R, F, G>( + data: *mut T, ceiling: u8, nvic_prio_bits: u8, t: &mut Threshold, @@ -30,10 +146,10 @@ unsafe fn claim<T, U, R, F, G>( ) -> R where F: FnOnce(U, &mut Threshold) -> R, - G: FnOnce(T) -> U, + G: FnOnce(*mut T) -> U, { let max_priority = 1 << nvic_prio_bits; - if ceiling > t.0 { + if ceiling > t.value { match () { #[cfg(armv6m)] () => { @@ -47,7 +163,7 @@ where let old = basepri::read(); let hw = (max_priority - ceiling) << (8 - nvic_prio_bits); basepri_max::write(hw); - let ret = f(g(data), &mut Threshold(ceiling)); + let ret = f(g(data), &mut Threshold::new(ceiling)); basepri::write(old); ret } @@ -58,139 +174,44 @@ where } } -pub struct Peripheral<P> -where - P: 'static, -{ - // FIXME(rustc/LLVM bug?) storing the ceiling in the resource de-optimizes - // claims (the ceiling value gets loaded at runtime rather than inlined) - // ceiling: u8, - peripheral: cortex_m::peripheral::Peripheral<P>, -} - -impl<P> Peripheral<P> { - pub const fn new(peripheral: cortex_m::peripheral::Peripheral<P>) -> Self { - Peripheral { peripheral } - } - - #[inline(always)] - pub unsafe fn borrow<'cs>( - &'static self, - _cs: &'cs CriticalSection, - ) -> &'cs P { - &*self.peripheral.get() - } - - #[inline(always)] - pub unsafe fn claim<R, F>( - &'static self, - ceiling: u8, - nvic_prio_bits: u8, - t: &mut Threshold, - f: F, - ) -> R - where - F: FnOnce(&P, &mut Threshold) -> R, - { - claim( - &self.peripheral, - ceiling, - nvic_prio_bits, - t, - f, - |peripheral| &*peripheral.get(), - ) - } - - pub fn get(&self) -> *mut P { - self.peripheral.get() - } -} - -unsafe impl<P> Sync for Peripheral<P> -where - P: Send, -{ -} - -pub struct Resource<T> { - // FIXME(rustc/LLVM bug?) storing the ceiling in the resource de-optimizes - // claims (the ceiling value gets loaded at runtime rather than inlined) - // ceiling: u8, +#[doc(hidden)] +pub struct Cell<T> { data: UnsafeCell<T>, } -impl<T> Resource<T> { - pub const fn new(value: T) -> Self { - Resource { - data: UnsafeCell::new(value), +#[doc(hidden)] +impl<T> Cell<T> { + pub const fn new(data: T) -> Self { + Cell { + data: UnsafeCell::new(data), } } - #[inline(always)] - pub unsafe fn borrow<'cs>( - &'static self, - _cs: &'cs CriticalSection, - ) -> &'cs Static<T> { - Static::ref_(&*self.data.get()) - } - - #[inline(always)] - pub unsafe fn borrow_mut<'cs>( - &'static self, - _cs: &'cs CriticalSection, - ) -> &'cs mut Static<T> { - Static::ref_mut(&mut *self.data.get()) - } - - #[inline(always)] - pub unsafe fn claim<R, F>( - &'static self, - ceiling: u8, - nvic_prio_bits: u8, - t: &mut Threshold, - f: F, - ) -> R - where - F: FnOnce(&Static<T>, &mut Threshold) -> R, - { - claim(&self.data, ceiling, nvic_prio_bits, t, f, |data| { - Static::ref_(&*data.get()) - }) - } - - #[inline(always)] - pub unsafe fn claim_mut<R, F>( - &'static self, - ceiling: u8, - nvic_prio_bits: u8, - t: &mut Threshold, - f: F, - ) -> R - where - F: FnOnce(&mut Static<T>, &mut Threshold) -> R, - { - claim(&self.data, ceiling, nvic_prio_bits, t, f, |data| { - Static::ref_mut(&mut *data.get()) - }) - } - pub fn get(&self) -> *mut T { self.data.get() } } -unsafe impl<T> Sync for Resource<T> +unsafe impl<T> Sync for Cell<T> where T: Send, { } -pub struct Threshold(u8); +/// Preemption threshold token +/// +/// The preemption threshold indicates the priority a task must have to preempt +/// the current context. For example a threshold of 2 indicates that only +/// interrupts / exceptions with a priority of 3 or greater can preempt the +/// current context +pub struct Threshold { + value: u8, +} impl Threshold { + #[doc(hidden)] pub unsafe fn new(value: u8) -> Self { - Threshold(value) + Threshold { value } } } @@ -206,14 +227,15 @@ where nvic.set_pending(interrupt); } +/// Binds a task `$handler` to the interrupt / exception `$NAME` #[macro_export] macro_rules! task { - ($NAME:ident, $body:path) => { + ($NAME:ident, $handler:path) => { #[allow(non_snake_case)] #[allow(unsafe_code)] #[no_mangle] pub unsafe extern "C" fn $NAME() { - let f: fn(&mut $crate::Threshold, ::$NAME::Resources) = $body; + let f: fn(&mut $crate::Threshold, ::$NAME::Resources) = $handler; f( &mut $crate::Threshold::new(::$NAME::$NAME), @@ -221,11 +243,13 @@ macro_rules! task { ); } }; - ($NAME:ident, $body:path, $local:ident { - $($var:ident: $ty:ty = $expr:expr;)+ + + ($NAME:ident, $handler:path, $locals:ident { + $(static $var:ident: $ty:ty = $expr:expr;)+ }) => { - struct $local { - $($var: $ty,)+ + #[allow(non_snake_case)] + struct $locals { + $($var: $crate::Static<$ty>,)+ } #[allow(non_snake_case)] @@ -234,17 +258,17 @@ macro_rules! task { pub unsafe extern "C" fn $NAME() { let f: fn( &mut $crate::Threshold, - &mut $local, + &mut $locals, ::$NAME::Resources, - ) = $body; + ) = $handler; - static mut LOCAL: $local = $local { - $($var: $expr,)+ + static mut LOCALS: $locals = $locals { + $($var: unsafe { $crate::Static::new($expr) },)+ }; f( &mut $crate::Threshold::new(::$NAME::$NAME), - &mut LOCAL, + &mut LOCALS, ::$NAME::Resources::new(), ); } |