diff options
| author | Jorge Aparicio <jorge@japaric.io> | 2018-11-03 17:02:41 +0100 |
|---|---|---|
| committer | Jorge Aparicio <jorge@japaric.io> | 2018-11-03 17:16:55 +0100 |
| commit | c631049efcadca8b07940c794cce2be58fa48444 (patch) | |
| tree | f6bd73e5c396fc06072557ee965cc59e9c8e3e9f /src/lib.rs | |
| parent | 653338e7997a0cdc5deaed98b1bb5f60006717ed (diff) | |
v0.4.0
closes #32
closes #33
Diffstat (limited to 'src/lib.rs')
| -rw-r--r-- | src/lib.rs | 436 |
1 files changed, 304 insertions, 132 deletions
@@ -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) } |
