diff --git a/examples/empty.rs b/examples/minimal.rs similarity index 100% rename from examples/empty.rs rename to examples/minimal.rs diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 5cb64266f7..b4040ebccf 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -16,7 +16,10 @@ quote = "0.5.1" # rtfm-syntax = "0.3.0" rtfm-syntax = { path = "../../rtfm-syntax" } syn = "0.13.1" -either = "1.5.0" + +[dependencies.either] +version = "1" +default-features = false [lib] proc-macro = true diff --git a/macros/src/trans.rs b/macros/src/trans.rs index f8ee2999eb..15b6d0af07 100644 --- a/macros/src/trans.rs +++ b/macros/src/trans.rs @@ -15,11 +15,17 @@ pub fn app(ctxt: &Context, app: &App) -> Tokens { root.push(quote! { extern crate cortex_m_rtfm as #k; + use #k::Resource as _cortex_m_rtfm_Resource; }); /* Resources */ let mut resources = vec![]; for (name, resource) in &app.resources { + if app.init.resources.contains(name) { + // `init` resources are handled below + continue; + } + let ty = &resource.ty; let expr = resource .expr @@ -52,6 +58,7 @@ pub fn app(ctxt: &Context, app: &App) -> Tokens { }); resources.push(quote! { + #[allow(non_camel_case_types)] pub struct #name { _not_send_or_sync: PhantomData<*const ()> } #[allow(dead_code)] @@ -73,6 +80,102 @@ pub fn app(ctxt: &Context, app: &App) -> Tokens { } }); + /* "resources" owned by `init` */ + // These don't implement the `Resource` trait because they are never shared. Instead they + // implement the `Singleton` trait and may or may not appear wrapped in `Uninit` + for (name, resource) in &app.resources { + if !app.init.resources.contains(name) { + // `init` resources are handled below + continue; + } + + let ty = &resource.ty; + let expr = resource.expr.as_ref().map(|e| quote!(#e)).unwrap_or_else(|| { + quote!(unsafe { #k::_impl::uninitialized() }) + }); + + // TODO replace this with a call to `heapless::singleton!` when it doesn't require a feature + // gate in the user code + root.push(quote! { + pub struct #name { _private: #k::_impl::Private } + + #[allow(unsafe_code)] + unsafe impl #k::_impl::Singleton for #name { + type Data = #ty; + + unsafe fn _var() -> &'static mut #ty { + static mut VAR: #ty = #expr; + + &mut VAR + } + } + + #[allow(unsafe_code)] + impl AsRef<#ty> for #name { + fn as_ref(&self) -> &#ty { + use #k::_impl::Singleton; + + unsafe { #name::_var() } + } + } + + #[allow(unsafe_code)] + impl AsMut<#ty> for #name { + fn as_mut(&mut self) -> &mut #ty { + use #k::_impl::Singleton; + + unsafe { #name::_var() } + } + } + + impl core::ops::Deref for #name { + type Target = #ty; + + fn deref(&self) -> &#ty { + self.as_ref() + } + } + + impl core::ops::DerefMut for #name { + fn deref_mut(&mut self) -> &mut #ty { + self.as_mut() + } + } + + #[allow(unsafe_code)] + impl Into<&'static mut #ty> for #name { + fn into(self) -> &'static mut #ty { + use #k::_impl::Singleton; + + unsafe { #name::_var() } + } + } + + #[allow(unsafe_code)] + unsafe impl #k::_impl::StableDeref for #name {} + }); + + if resource.expr.is_some() { + root.push(quote! { + impl #name { + #[allow(unsafe_code)] + unsafe fn _new() -> Self { + #name { _private: #k::_impl::Private::new() } + } + } + }); + } else { + root.push(quote! { + impl #name { + #[allow(unsafe_code)] + unsafe fn _new() -> #k::_impl::Uninit { + #k::_impl::Uninit::new(#name { _private: #k::_impl::Private::new() }) + } + } + }); + } + } + /* Tasks */ for (name, task) in &app.tasks { let path = &task.path; @@ -851,15 +954,18 @@ pub fn app(ctxt: &Context, app: &App) -> Tokens { .resources .iter() .map(|r| { - let ty = &app.resources[r].ty; - quote!(#r: &'static mut #ty) + if app.resources[r].expr.is_some() { + quote!(pub #r: ::#r) + } else { + quote!(pub #r: #k::_impl::Uninit<::#r>) + } }) .collect::>(); let res_exprs = app.init .resources .iter() - .map(|r| quote!(#r: _resource::#r::_var())) + .map(|r| quote!(#r: #r::_new())) .collect::>(); let tasks_fields = app.init @@ -895,7 +1001,7 @@ pub fn app(ctxt: &Context, app: &App) -> Tokens { let late_resources = app.resources .iter() .filter_map(|(name, res)| { - if res.expr.is_none() { + if res.expr.is_none() && !app.init.resources.contains(name) { let ty = &res.ty; Some(quote!(pub #name: #ty)) } else { @@ -993,7 +1099,7 @@ pub fn app(ctxt: &Context, app: &App) -> Tokens { // Initialize LateResources for (name, res) in &app.resources { - if res.expr.is_none() { + if res.expr.is_none() && !app.init.resources.contains(name) { post_init.push(quote! { core::ptr::write(_resource::#name::_var(), _lr.#name); }); diff --git a/src/_impl/mod.rs b/src/_impl/mod.rs index b77286cb7f..79bf034320 100644 --- a/src/_impl/mod.rs +++ b/src/_impl/mod.rs @@ -5,7 +5,11 @@ pub use self::tq::{dispatch, NotReady, TimerQueue}; pub use cortex_m::interrupt; use cortex_m::interrupt::Nr; pub use cortex_m::peripheral::syst::SystClkSource; -use cortex_m::peripheral::{CBP, CPUID, DCB, DWT, FPB, FPU, ITM, MPU, NVIC, SCB, SYST, TPIU}; +use cortex_m::peripheral::{CBP, CPUID, DCB, FPB, FPU, ITM, MPU, NVIC, SCB, TPIU}; +#[cfg(not(feature = "timer-queue"))] +use cortex_m::peripheral::{DWT, SYST}; +pub use heapless::object_pool::{Singleton, Uninit}; +pub use stable_deref_trait::StableDeref; use heapless::RingBuffer as Queue; pub use typenum::consts::*; pub use typenum::{Max, Maximum, Unsigned}; @@ -16,6 +20,16 @@ mod tq; pub type FreeQueue = Queue; pub type ReadyQueue = Queue<(T, u8), N, u8>; +pub struct Private { + _0: (), +} + +impl Private { + pub unsafe fn new() -> Self { + Private { _0: () } + } +} + #[allow(non_snake_case)] #[cfg(feature = "timer-queue")] pub struct Peripherals<'a> { @@ -33,7 +47,7 @@ pub struct Peripherals<'a> { #[allow(non_snake_case)] #[cfg(not(feature = "timer-queue"))] -pub struct Peripherals { +pub struct Peripherals<'a> { pub CBP: CBP, pub CPUID: CPUID, pub DCB: DCB, @@ -43,7 +57,7 @@ pub struct Peripherals { pub ITM: ITM, pub MPU: MPU, // pub NVIC: NVIC, - pub SCB: SCB, + pub SCB: &'a mut SCB, pub SYST: SYST, pub TPIU: TPIU, } diff --git a/src/event_task.rs b/src/event_task.rs new file mode 100644 index 0000000000..9910413bfb --- /dev/null +++ b/src/event_task.rs @@ -0,0 +1,39 @@ +//! An event task, a task that starts in response to an event (interrupt source) + +use Priority; + +/// The execution context of this event task +pub struct Context { + /// The time at which this task started executing + /// + /// *NOTE* that this is not the *arrival* time of the event that started this task. Due to + /// prioritization of other tasks this task could have started much later than the time the + /// event arrived at. + /// + /// *This field is only available if the `"timer-queue"` feature is enabled* + pub baseline: u32, + + /// The input of this task + pub input: Input, + + /// The starting priority of this task + pub priority: Priority

, + + /// Resources assigned to this event task + pub resources: Resources, + + /// Tasks that this event task can schedule + pub tasks: Tasks, +} + +#[doc(hidden)] +pub struct Input; + +#[doc(hidden)] +pub struct P; + +/// Resources assigned to this event task +pub struct Resources {} + +/// Tasks that this event task can schedule +pub struct Tasks {} diff --git a/src/idle.rs b/src/idle.rs new file mode 100644 index 0000000000..e9ecdba00d --- /dev/null +++ b/src/idle.rs @@ -0,0 +1,24 @@ +//! The `idle` function + +use Priority; + +use typenum::consts::U0; + +/// The execution context of `idle` +pub struct Context { + /// The starting priority of `idle` + pub priority: Priority, + + /// Resources assigned to `idle` + pub resources: Resources, +} + +/// Resources assigned to `idle` +#[allow(non_snake_case)] +pub struct Resources { + /// Example of a resource assigned to `idle` + pub KEY: KEY, +} + +#[doc(hidden)] +pub struct KEY; diff --git a/src/init.rs b/src/init.rs new file mode 100644 index 0000000000..2b40ef4821 --- /dev/null +++ b/src/init.rs @@ -0,0 +1,66 @@ +//! The `init`ialization function +use typenum::consts::U255; + +use Priority; +pub use _impl::Peripherals as Core; + +/// Execution context of `init` +pub struct Context<'a> { + /// Core (Cortex-M) peripherals + pub core: Core<'a>, + /// Device specific peripherals + pub device: Device, + /// The priority of `init` + pub priority: Priority, + /// Resources assigned to `init` + pub resources: Resources, + /// Tasks that `init` can schedule + pub tasks: Tasks, +} + +/// Device specific peripherals +/// +/// The contents of this `struct` will depend on the selected `device` +#[allow(non_snake_case)] +pub struct Device { + /// Example + pub GPIOA: GPIOA, + /// Example + pub TIM2: TIM2, + /// Example + pub USART1: USART1, + _more: (), +} + +#[doc(hidden)] +pub struct GPIOA; + +#[doc(hidden)] +pub struct RCC; + +#[doc(hidden)] +pub struct TIM2; + +#[doc(hidden)] +pub struct USART1; + +/// The initial value of resources that were not given an initial value in `app.resources` +#[allow(non_snake_case)] +pub struct LateResources { + /// Example of a resource that's initialized "late", or at runtime + pub KEY: [u8; 128], + _more: (), +} + +/// Resources assigned to and owned by `init` +#[allow(non_snake_case)] +pub struct Resources { + /// Example of a resource assigned to `init` + pub BUFFER: BUFFER, +} + +#[doc(hidden)] +pub struct BUFFER; + +/// Tasks that `init` can schedule +pub struct Tasks {} diff --git a/src/lib.rs b/src/lib.rs index 69e122cb4a..b02977c8f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,146 @@ -//! Real Time for The Masses: high performance, predictable, bare metal task scheduler +//! Real Time for The Masses: a high performance and predictable bare metal task scheduler +//! +//! # Features +//! +//! - Priority based scheduler implemented mostly in hardware with minimal bookkeeping and overhead. +//! - Tasks can be started in response to events or scheduled on-demand +//! - Message passing between tasks +//! - Data race free sharing of resources (e.g. memory) between tasks using the Priority Ceiling +//! Protocol (PCP). +//! - Guaranteed dead lock free execution +//! - Doesn't need a memory allocator to operate +//! +//! # User guide and internal documentation +//! +//! Check [the RTFM book] instead. These auto-generated docs only contain the API reference and some +//! examples. +//! +//! [the RTFM book]: TODO +//! +//! # `app!` +//! +//! The `app!` macro contains the specification of an application. It declares the tasks that +//! compose the application of and how resources (`static` variables) are distributed across them. +//! +//! This section describes the syntax of the `app!` macro. +//! +//! ## `app.device` +//! +//! ``` ignore +//! app! { +//! device: some::path, +//! } +//! ``` +//! +//! This field specifies the target device as a path to a crate generated using [`svd2rust`] +//! v0.13.x. +//! +//! [`svd2rust`]: https://crates.io/crates/svd2rust +//! +//! ## `app.resources` +//! +//! This section contains a list of `static` variables that will be used as resources. These +//! variables don't need to be assigned an initial value. If a resource lacks an initial value it +//! will have to be assigned one in `init`. +//! +//! ``` ignore +//! app! { +//! resources: { +//! // Resource with initial value; its initial value is stored in Flash +//! static STATE: bool = false; +//! +//! // Resource without initial value; it will be initialized at runtime +//! static KEY: [u8; 128]; +//! +//! // .. +//! } +//! } +//! ``` +//! +//! ## `app.free_interrupts` +//! +//! This a list of interrupts that the RTFM runtime can use to dispatch on-demand tasks. +//! +//! ## `app.init` +//! +//! This section describes the context of the [`init`][fn@init]ialization function. +//! +//! ``` ignore +//! app! { +//! init: { +//! body: some::path, +//! resources: [A, B], +//! schedule_now: [on_demand_task], +//! schedule_after: [task_a], +//! } +//! } +//! ``` +//! +//! ### `app.init.body` +//! +//! This is the path to the `init` function. If omitted this field will default to `init`. +//! +//! ### `app.init.resources` +//! +//! The resources assigned to, and owned by, `init`. This field is optional; if omitted this field +//! defaults to an empty list. +//! +//! ### `app.init.schedule_now` / `app.init.schedule_after` +//! +//! List of tasks `init` can schedule via the [`schedule_now`] and [`schedule_after`] APIs. +//! +//! [`schedule_now`]: trait.ScheduleNow.html +//! [`schedule_after`]: trait.ScheduleAfter.html +//! +//! ## `app.idle` +//! +//! ## `app.tasks` +//! +//! This section contains a list of tasks. These tasks can be event tasks or on-demand tasks. +//! +//! ``` ignore +//! app! { +//! tasks: { +//! event_task: { +//! body: some::path, +//! interrupt: USART1, +//! resources: [STATE], +//! schedule_now: [on_demand_task], +//! schedule_after: [task_a], +//! }, +//! +//! on_demand_task: { +//! body: some::other::path, +//! instances: 2, +//! resources: [STATE], +//! schedule_now: [on_demand_task], +//! schedule_after: [task_a], +//! }, +//! +//! // more tasks here +//! } +//! } +//! ``` +//! +//! ### `app.tasks.$.body` +//! +//! The path to the body of the task. This field is optional; if omitted the path defaults to the +//! name of the task. +//! +//! ### `app.tasks.$.interrupt` +//! +//! Event tasks only. This is the event, or interrupt source, that triggers the execution of the +//! task. +//! +//! ### `app.tasks.$.instances` +//! +//! On-demand tasks only. The maximum number of times this task can be scheduled and remain in a +//! pending execution, or ready, state. This field is optional; if omitted, it defaults to `1`. +//! +//! ### `app.tasks.$.resources` +//! +//! The resources assigned to this task. This field is optional; if omitted this field defaults to +//! an empty list. #![allow(warnings)] #![deny(missing_docs)] @@ -13,6 +155,7 @@ extern crate cortex_m; extern crate cortex_m_rtfm_macros; extern crate heapless; extern crate typenum; +extern crate stable_deref_trait; use cortex_m::interrupt; pub use cortex_m_rtfm_macros::app; @@ -23,6 +166,10 @@ pub use resource::{Priority, Resource}; #[doc(hidden)] pub mod _impl; +pub mod event_task; +pub mod idle; +pub mod init; +pub mod on_demand_task; mod resource; /// Executes the given closure atomically @@ -47,3 +194,57 @@ where } } } + +/// The `init`ialization function takes care of system and resource initialization +pub fn init(_ctxt: init::Context) -> init::LateResources { + unimplemented!() +} + +/// When no task is being executed the processor resumes the execution of the `idle` function +pub fn idle(_ctxt: idle::Context) -> ! { + unimplemented!() +} + +/// The `schedule_now` interface +pub trait ScheduleNow { + /// Optional message sent to the scheduled task + type Payload; + + /// Schedules a task to run right away + /// + /// This method will return an error if the maximum number of `instances` of the task are + /// already pending execution. + /// + /// If `"timer-queue"` is enabled the newly scheduled task will inherit the `baseline` of the + /// *current* task. + /// + /// *NOTE* that the `payload` argument is not required if the task has no input, i.e. its input + /// type is `()` + fn schedule_now

( + &mut self, + priority: &mut Priority

, + payload: Self::Payload, + ) -> Result<(), Self::Payload>; +} + +/// The `schedule_after` interface +/// +/// *NOTE* that this API is only available if the `"timer-queue"` feature is enabled. +pub trait ScheduleAfter { + /// Optional message sent to the scheduled task + type Payload; + + /// Schedules a task to run `offset` ticks after the *current* task `baseline`. + /// + /// This method will return an error if the maximum number of instances of the task are pending + /// execution. + /// + /// *NOTE* that the `payload` argument is not required if the task has no input, i.e. its input + /// type is `()` + fn schedule_after

( + &mut self, + priority: &mut Priority

, + offset: u32, + payload: Self::Payload, + ) -> Result<(), Self::Payload>; +} diff --git a/src/on_demand_task.rs b/src/on_demand_task.rs new file mode 100644 index 0000000000..72f682a3c2 --- /dev/null +++ b/src/on_demand_task.rs @@ -0,0 +1,39 @@ +//! An schedulable task + +use Priority; + +/// The execution context of this schedulable task +pub struct Context { + /// The time at which this task was scheduled to run + /// + /// *NOTE* that this is not the *start* time of the task. Due to scheduling overhead a task will + /// always start a bit later than its scheduled time. Also due to prioritization of other tasks + /// a task may start much later than its scheduled time. + /// + /// *This field is only available if the `"timer-queue"` feature is enabled* + pub baseline: u32, + + /// The input of this task + pub input: Input, + + /// The starting priority of this task + pub priority: Priority

, + + /// Resources assigned to this event task + pub resources: Resources, + + /// Tasks that this event task can schedule + pub tasks: Tasks, +} + +#[doc(hidden)] +pub struct Input; + +#[doc(hidden)] +pub struct P; + +/// Resources assigned to this event task +pub struct Resources {} + +/// Tasks that this event task can schedule +pub struct Tasks {} diff --git a/src/tq.rs b/src/tq.rs new file mode 100644 index 0000000000..31fd101b9d --- /dev/null +++ b/src/tq.rs @@ -0,0 +1,125 @@ +use core::cmp::{self, Ordering}; + +use cortex_m::peripheral::{SCB, SYST}; +use heapless::binary_heap::{BinaryHeap, Min}; +use heapless::ArrayLength; +use typenum::{Max, Maximum, Unsigned}; + +use instant::Instant; +use resource::{Resource, Threshold}; + +pub struct Message { + pub baseline: Instant, + pub index: u8, + pub task: T, +} + +impl Eq for Message {} + +impl Ord for Message { + fn cmp(&self, other: &Message) -> Ordering { + self.baseline.cmp(&other.baseline) + } +} + +impl PartialEq for Message { + fn eq(&self, other: &Message) -> bool { + self.baseline == other.baseline + } +} + +impl PartialOrd for Message { + fn partial_cmp(&self, other: &Message) -> Option { + Some(self.cmp(other)) + } +} + +#[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, m: Message) { + let mut is_empty = true; + if self.queue + .peek() + .map(|head| { + is_empty = false; + m.baseline < head.baseline + }) + .unwrap_or(true) + { + if is_empty { + self.syst.enable_interrupt(); + } + + // set SysTick pending + unsafe { (*SCB::ptr()).icsr.write(1 << 26) } + } + + self.queue.push_unchecked(m); + } +} + +pub fn dispatch(t: &mut Threshold

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

, T, u8), + N: 'static + ArrayLength>, + P: Max + Unsigned, + T: 'static + Copy + Send, + TQ: Resource>, + TQ::Ceiling: Unsigned, +{ + loop { + let next = tq.claim_mut(t, |tq, _| { + if let Some(bl) = tq.queue.peek().map(|p| p.baseline) { + let diff = bl - Instant::now(); + + if diff < 0 { + // message 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 from the new reload + tq.syst.clear_current(); + + None + } + } else { + // empty queue + tq.syst.disable_interrupt(); + None + } + }); + + if let Some((task, index)) = next { + f(t, task, index) + } else { + return; + } + } +}