This commit is contained in:
Jorge Aparicio 2018-05-17 21:52:03 +02:00
parent 14fedeb342
commit b1abd52be2
10 changed files with 627 additions and 10 deletions

View file

@ -16,7 +16,10 @@ quote = "0.5.1"
# rtfm-syntax = "0.3.0" # rtfm-syntax = "0.3.0"
rtfm-syntax = { path = "../../rtfm-syntax" } rtfm-syntax = { path = "../../rtfm-syntax" }
syn = "0.13.1" syn = "0.13.1"
either = "1.5.0"
[dependencies.either]
version = "1"
default-features = false
[lib] [lib]
proc-macro = true proc-macro = true

View file

@ -15,11 +15,17 @@ pub fn app(ctxt: &Context, app: &App) -> Tokens {
root.push(quote! { root.push(quote! {
extern crate cortex_m_rtfm as #k; extern crate cortex_m_rtfm as #k;
use #k::Resource as _cortex_m_rtfm_Resource;
}); });
/* Resources */ /* Resources */
let mut resources = vec![]; let mut resources = vec![];
for (name, resource) in &app.resources { for (name, resource) in &app.resources {
if app.init.resources.contains(name) {
// `init` resources are handled below
continue;
}
let ty = &resource.ty; let ty = &resource.ty;
let expr = resource let expr = resource
.expr .expr
@ -52,6 +58,7 @@ pub fn app(ctxt: &Context, app: &App) -> Tokens {
}); });
resources.push(quote! { resources.push(quote! {
#[allow(non_camel_case_types)]
pub struct #name { _not_send_or_sync: PhantomData<*const ()> } pub struct #name { _not_send_or_sync: PhantomData<*const ()> }
#[allow(dead_code)] #[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<Self> {
#k::_impl::Uninit::new(#name { _private: #k::_impl::Private::new() })
}
}
});
}
}
/* Tasks */ /* Tasks */
for (name, task) in &app.tasks { for (name, task) in &app.tasks {
let path = &task.path; let path = &task.path;
@ -851,15 +954,18 @@ pub fn app(ctxt: &Context, app: &App) -> Tokens {
.resources .resources
.iter() .iter()
.map(|r| { .map(|r| {
let ty = &app.resources[r].ty; if app.resources[r].expr.is_some() {
quote!(#r: &'static mut #ty) quote!(pub #r: ::#r)
} else {
quote!(pub #r: #k::_impl::Uninit<::#r>)
}
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let res_exprs = app.init let res_exprs = app.init
.resources .resources
.iter() .iter()
.map(|r| quote!(#r: _resource::#r::_var())) .map(|r| quote!(#r: #r::_new()))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let tasks_fields = app.init let tasks_fields = app.init
@ -895,7 +1001,7 @@ pub fn app(ctxt: &Context, app: &App) -> Tokens {
let late_resources = app.resources let late_resources = app.resources
.iter() .iter()
.filter_map(|(name, res)| { .filter_map(|(name, res)| {
if res.expr.is_none() { if res.expr.is_none() && !app.init.resources.contains(name) {
let ty = &res.ty; let ty = &res.ty;
Some(quote!(pub #name: #ty)) Some(quote!(pub #name: #ty))
} else { } else {
@ -993,7 +1099,7 @@ pub fn app(ctxt: &Context, app: &App) -> Tokens {
// Initialize LateResources // Initialize LateResources
for (name, res) in &app.resources { 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! { post_init.push(quote! {
core::ptr::write(_resource::#name::_var(), _lr.#name); core::ptr::write(_resource::#name::_var(), _lr.#name);
}); });

View file

@ -5,7 +5,11 @@ pub use self::tq::{dispatch, NotReady, TimerQueue};
pub use cortex_m::interrupt; pub use cortex_m::interrupt;
use cortex_m::interrupt::Nr; use cortex_m::interrupt::Nr;
pub use cortex_m::peripheral::syst::SystClkSource; 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; use heapless::RingBuffer as Queue;
pub use typenum::consts::*; pub use typenum::consts::*;
pub use typenum::{Max, Maximum, Unsigned}; pub use typenum::{Max, Maximum, Unsigned};
@ -16,6 +20,16 @@ mod tq;
pub type FreeQueue<N> = Queue<u8, N, u8>; pub type FreeQueue<N> = Queue<u8, N, u8>;
pub type ReadyQueue<T, N> = Queue<(T, u8), N, u8>; pub type ReadyQueue<T, N> = Queue<(T, u8), N, u8>;
pub struct Private {
_0: (),
}
impl Private {
pub unsafe fn new() -> Self {
Private { _0: () }
}
}
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
pub struct Peripherals<'a> { pub struct Peripherals<'a> {
@ -33,7 +47,7 @@ pub struct Peripherals<'a> {
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[cfg(not(feature = "timer-queue"))] #[cfg(not(feature = "timer-queue"))]
pub struct Peripherals { pub struct Peripherals<'a> {
pub CBP: CBP, pub CBP: CBP,
pub CPUID: CPUID, pub CPUID: CPUID,
pub DCB: DCB, pub DCB: DCB,
@ -43,7 +57,7 @@ pub struct Peripherals {
pub ITM: ITM, pub ITM: ITM,
pub MPU: MPU, pub MPU: MPU,
// pub NVIC: NVIC, // pub NVIC: NVIC,
pub SCB: SCB, pub SCB: &'a mut SCB,
pub SYST: SYST, pub SYST: SYST,
pub TPIU: TPIU, pub TPIU: TPIU,
} }

39
src/event_task.rs Normal file
View file

@ -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<P>,
/// 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 {}

24
src/idle.rs Normal file
View file

@ -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<U0>,
/// 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;

66
src/init.rs Normal file
View file

@ -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<U255>,
/// 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 {}

View file

@ -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)] #![allow(warnings)]
#![deny(missing_docs)] #![deny(missing_docs)]
@ -13,6 +155,7 @@ extern crate cortex_m;
extern crate cortex_m_rtfm_macros; extern crate cortex_m_rtfm_macros;
extern crate heapless; extern crate heapless;
extern crate typenum; extern crate typenum;
extern crate stable_deref_trait;
use cortex_m::interrupt; use cortex_m::interrupt;
pub use cortex_m_rtfm_macros::app; pub use cortex_m_rtfm_macros::app;
@ -23,6 +166,10 @@ pub use resource::{Priority, Resource};
#[doc(hidden)] #[doc(hidden)]
pub mod _impl; pub mod _impl;
pub mod event_task;
pub mod idle;
pub mod init;
pub mod on_demand_task;
mod resource; mod resource;
/// Executes the given closure atomically /// 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<P>(
&mut self,
priority: &mut Priority<P>,
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<P>(
&mut self,
priority: &mut Priority<P>,
offset: u32,
payload: Self::Payload,
) -> Result<(), Self::Payload>;
}

39
src/on_demand_task.rs Normal file
View file

@ -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<P>,
/// 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 {}

125
src/tq.rs Normal file
View file

@ -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<T> {
pub baseline: Instant,
pub index: u8,
pub task: T,
}
impl<T> Eq for Message<T> {}
impl<T> Ord for Message<T> {
fn cmp(&self, other: &Message<T>) -> Ordering {
self.baseline.cmp(&other.baseline)
}
}
impl<T> PartialEq for Message<T> {
fn eq(&self, other: &Message<T>) -> bool {
self.baseline == other.baseline
}
}
impl<T> PartialOrd for Message<T> {
fn partial_cmp(&self, other: &Message<T>) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[doc(hidden)]
pub struct TimerQueue<T, N>
where
N: ArrayLength<Message<T>>,
T: Copy,
{
pub syst: SYST,
pub queue: BinaryHeap<Message<T>, N, Min>,
}
impl<T, N> TimerQueue<T, N>
where
N: ArrayLength<Message<T>>,
T: Copy,
{
pub const fn new(syst: SYST) -> Self {
TimerQueue {
syst,
queue: BinaryHeap::new(),
}
}
#[inline]
pub unsafe fn enqueue(&mut self, m: Message<T>) {
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, TQ, N, F, P>(t: &mut Threshold<P>, tq: &mut TQ, mut f: F)
where
F: FnMut(&mut Threshold<P>, T, u8),
N: 'static + ArrayLength<Message<T>>,
P: Max<TQ::Ceiling> + Unsigned,
T: 'static + Copy + Send,
TQ: Resource<Data = TimerQueue<T, N>>,
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;
}
}
}