This commit is contained in:
Jorge Aparicio 2018-04-29 08:45:31 +02:00
parent 754f041ae0
commit 8723c6d45b
41 changed files with 1974 additions and 2851 deletions

View file

@ -13,8 +13,10 @@ version = "0.3.1"
failure = "0.1.1"
proc-macro2 = "0.3.6"
quote = "0.5.1"
rtfm-syntax = "0.3.0"
# rtfm-syntax = "0.3.0"
rtfm-syntax = { git = "https://github.com/japaric/rtfm-syntax", branch = "tq" }
syn = "0.13.1"
either = "1.5.0"
[lib]
proc-macro = true

View file

@ -1,77 +1,260 @@
use std::cmp;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use syn::Ident;
use either::Either;
use syn::{Ident, Type};
use syntax::check::App;
use check::App;
pub fn app(app: &App) -> Context {
let mut async = HashSet::new();
let mut async_after = HashSet::new();
let mut dispatchers = HashMap::new();
let mut triggers = HashMap::new();
let mut tq = TimerQueue::new();
let mut free_interrupts = app.free_interrupts.iter().cloned().collect::<Vec<_>>();
pub type Ownerships = HashMap<Ident, Ownership>;
async.extend(&app.init.async);
async_after.extend(&app.init.async_after);
pub enum Ownership {
/// Owned or co-owned by tasks that run at the same priority
Owned { priority: u8 },
/// Shared by tasks that run at different priorities.
///
/// `ceiling` is the maximum value across all the task priorities
Shared { ceiling: u8 },
}
impl Ownership {
pub fn ceiling(&self) -> u8 {
match *self {
Ownership::Owned { priority } => priority,
Ownership::Shared { ceiling } => ceiling,
}
}
pub fn is_owned(&self) -> bool {
match *self {
Ownership::Owned { .. } => true,
_ => false,
}
}
}
pub fn app(app: &App) -> Ownerships {
let mut ownerships = HashMap::new();
for resource in &app.idle.resources {
ownerships.insert(resource.clone(), Ownership::Owned { priority: 0 });
}
for task in app.tasks.values() {
for resource in task.resources.iter() {
if let Some(ownership) = ownerships.get_mut(resource) {
match *ownership {
Ownership::Owned { priority } => {
if priority == task.priority {
*ownership = Ownership::Owned { priority };
} else {
*ownership = Ownership::Shared {
ceiling: cmp::max(priority, task.priority),
};
}
}
Ownership::Shared { ceiling } => {
if task.priority > ceiling {
*ownership = Ownership::Shared {
ceiling: task.priority,
};
}
}
}
continue;
// compute dispatchers
for (name, task) in &app.tasks {
match task.interrupt_or_capacity {
Either::Left(interrupt) => {
triggers.insert(interrupt, (*name, task.priority));
}
Either::Right(capacity) => {
let dispatcher = dispatchers.entry(task.priority).or_insert(Dispatcher::new(
free_interrupts.pop().expect("not enough free interrupts"),
));
dispatcher.tasks.push(*name);
dispatcher.capacity += capacity;
}
}
ownerships.insert(
resource.clone(),
Ownership::Owned {
priority: task.priority,
},
);
for task in &task.async {
async.insert(*task);
}
for task in &task.async_after {
async_after.insert(*task);
// Timer queue
if let Entry::Vacant(entry) = tq.tasks.entry(*task) {
tq.capacity += app.tasks[task].interrupt_or_capacity.right().unwrap();
entry.insert(app.tasks[task].priority);
}
}
}
ownerships
// The SysTick exception runs at the highest dispatcher priority
let sys_tick = dispatchers.keys().cloned().max().unwrap_or(1);
// compute ceilings
let mut ceilings = Ceilings::new(sys_tick);
// the SysTick interrupt contends for the timer queue (this has been accounted for in the
// `Ceilings` constructor) and for the producer end of all the dispatcher queues (__#N::Q)
for dispatch_priority in dispatchers.keys() {
ceilings
.dispatch_queues
.insert(*dispatch_priority, sys_tick);
}
// resources
for (priority, resource) in app.idle.resources.iter().map(|res| (0, res)).chain(
app.tasks
.iter()
.flat_map(|(name, task)| task.resources.iter().map(move |res| (task.priority, res))),
) {
let ceiling = ceilings
.resources
.entry(*resource)
.or_insert(Ceiling::Owned(priority));
if priority > (*ceiling).into() {
*ceiling = Ceiling::Shared(priority);
} else if priority < (*ceiling).into() && ceiling.is_owned() {
*ceiling = Ceiling::Shared((*ceiling).into());
}
}
// async
for (caller_priority, task) in app.tasks
.values()
.flat_map(|caller| caller.async.iter().map(move |task| (caller.priority, task)))
{
// async callers contend for the consumer end of the task slot queue (#task::SQ) and ...
let ceiling = ceilings.slot_queues.entry(*task).or_insert(caller_priority);
if caller_priority > *ceiling {
*ceiling = caller_priority;
}
// .. for the producer end of the dispatcher queue (__#dispatch_priority::Q)
let dispatch_priority = app.tasks[task].priority;
let ceiling = ceilings
.dispatch_queues
.entry(dispatch_priority)
.or_insert(dispatch_priority);
if caller_priority > *ceiling {
*ceiling = caller_priority;
}
}
// async_after
for (caller_priority, task) in app.tasks.values().flat_map(|caller| {
caller
.async_after
.iter()
.map(move |task| (caller.priority, task))
}) {
// async_after callers contend for the consumer end of the task slot queue (#task::SQ) and
// ...
let ceiling = ceilings.slot_queues.entry(*task).or_insert(caller_priority);
if caller_priority > *ceiling {
*ceiling = caller_priority;
}
// .. for the timer queue
if caller_priority > ceilings.timer_queue {
ceilings.timer_queue = caller_priority;
}
}
Context {
async,
async_after,
ceilings,
dispatchers,
sys_tick,
triggers,
timer_queue: tq,
}
}
pub struct Context {
// set of `async` tasks
pub async: HashSet<Ident>,
// set of `async_after` tasks
pub async_after: HashSet<Ident>,
pub ceilings: Ceilings,
// Priority:u8 -> Dispatcher
pub dispatchers: HashMap<u8, Dispatcher>,
// Interrupt:Ident -> Task:Ident
pub triggers: HashMap<Ident, (Ident, u8)>,
pub timer_queue: TimerQueue,
// priority of the SysTick exception
pub sys_tick: u8,
}
pub struct TimerQueue {
// Task:Ident -> Priority:u8
tasks: HashMap<Ident, u8>,
capacity: u8,
}
impl TimerQueue {
fn new() -> Self {
TimerQueue {
tasks: HashMap::new(),
capacity: 0,
}
}
pub fn capacity(&self) -> u8 {
self.capacity
}
pub fn tasks(&self) -> &HashMap<Ident, u8> {
&self.tasks
}
}
pub struct Dispatcher {
capacity: u8,
interrupt: Ident,
tasks: Vec<Ident>,
}
impl Dispatcher {
fn new(interrupt: Ident) -> Self {
Dispatcher {
capacity: 0,
interrupt,
tasks: vec![],
}
}
pub fn capacity(&self) -> u8 {
self.capacity
}
pub fn interrupt(&self) -> Ident {
self.interrupt
}
pub fn tasks(&self) -> &[Ident] {
&self.tasks
}
}
#[derive(Debug)]
pub struct Ceilings {
dispatch_queues: HashMap<u8, u8>,
resources: HashMap<Ident, Ceiling>,
slot_queues: HashMap<Ident, u8>,
timer_queue: u8,
}
impl Ceilings {
fn new(sys_tick_priority: u8) -> Self {
Ceilings {
dispatch_queues: HashMap::new(),
resources: HashMap::new(),
slot_queues: HashMap::new(),
timer_queue: sys_tick_priority,
}
}
pub fn dispatch_queues(&self) -> &HashMap<u8, u8> {
&self.dispatch_queues
}
pub fn resources(&self) -> &HashMap<Ident, Ceiling> {
&self.resources
}
pub fn slot_queues(&self) -> &HashMap<Ident, u8> {
&self.slot_queues
}
pub fn timer_queue(&self) -> u8 {
self.timer_queue
}
}
#[derive(Clone, Copy, Debug)]
pub enum Ceiling {
Owned(u8),
Shared(u8),
}
impl Ceiling {
pub fn is_owned(&self) -> bool {
if let Ceiling::Owned(..) = *self {
true
} else {
false
}
}
}
impl From<Ceiling> for u8 {
fn from(ceiling: Ceiling) -> u8 {
match ceiling {
Ceiling::Owned(prio) => prio,
Ceiling::Shared(ceil) => ceil,
}
}
}

View file

@ -1,95 +1,7 @@
use std::collections::HashMap;
use syntax::check::App;
use syntax::Result;
use syn::{Ident, Path};
use syntax::check::{self, Idents, Idle, Init, Statics};
use syntax::{self, Result};
pub struct App {
pub device: Path,
pub idle: Idle,
pub init: Init,
pub resources: Statics,
pub tasks: Tasks,
}
pub type Tasks = HashMap<Ident, Task>;
#[allow(non_camel_case_types)]
pub enum Exception {
PENDSV,
SVCALL,
SYS_TICK,
}
impl Exception {
pub fn from(s: &str) -> Option<Self> {
Some(match s {
"PENDSV" => Exception::PENDSV,
"SVCALL" => Exception::SVCALL,
"SYS_TICK" => Exception::SYS_TICK,
_ => return None,
})
}
pub fn nr(&self) -> usize {
match *self {
Exception::PENDSV => 14,
Exception::SVCALL => 11,
Exception::SYS_TICK => 15,
}
}
}
pub enum Kind {
Exception(Exception),
Interrupt { enabled: bool },
}
pub struct Task {
pub kind: Kind,
pub path: Path,
pub priority: u8,
pub resources: Idents,
}
pub fn app(app: check::App) -> Result<App> {
let app = App {
device: app.device,
idle: app.idle,
init: app.init,
resources: app.resources,
tasks: app.tasks
.into_iter()
.map(|(k, v)| {
let v = ::check::task(k.as_ref(), v)?;
Ok((k, v))
})
.collect::<Result<_>>()?,
};
Ok(app)
}
fn task(name: &str, task: syntax::check::Task) -> Result<Task> {
let kind = match Exception::from(name) {
Some(e) => {
ensure!(
task.enabled.is_none(),
"`enabled` field is not valid for exceptions"
);
Kind::Exception(e)
}
None => Kind::Interrupt {
enabled: task.enabled.unwrap_or(true),
},
};
Ok(Task {
kind,
path: task.path,
priority: task.priority.unwrap_or(1),
resources: task.resources,
})
pub fn app(_app: &App) -> Result<()> {
// TODO ???
Ok(())
}

View file

@ -1,7 +1,7 @@
//! Procedural macros of the `cortex-m-rtfm` crate
// #![deny(warnings)]
#![allow(warnings)]
#![feature(proc_macro)]
#![recursion_limit = "128"]
#![recursion_limit = "256"]
#[macro_use]
extern crate failure;
@ -10,6 +10,7 @@ extern crate proc_macro2;
extern crate syn;
#[macro_use]
extern crate quote;
extern crate either;
extern crate rtfm_syntax as syntax;
use proc_macro::TokenStream;
@ -19,154 +20,6 @@ mod analyze;
mod check;
mod trans;
/// The `app!` macro, a macro used to specify the tasks and resources of a RTFM application.
///
/// The contents of this macro uses a `key: value` syntax. All the possible keys are shown below:
///
/// ``` text
/// app! {
/// device: ..,
///
/// resources: { .. },
///
/// init: { .. },
///
/// idle: { .. },
///
/// tasks: { .. },
/// }
/// ```
///
/// # `device`
///
/// The value of this key is a Rust path, like `foo::bar::baz`, that must point to a *device crate*,
/// a crate generated using `svd2rust`.
///
/// # `resources`
///
/// This key is optional. Its value is a list of `static` variables. These variables are the data
/// that can be safely accessed, modified and shared by tasks.
///
/// ``` text
/// resources: {
/// static A: bool = false;
/// static B: i32 = 0;
/// static C: [u8; 16] = [0; 16];
/// static D: Thing = Thing::new(..);
/// static E: Thing;
/// }
/// ```
///
/// The initial value of a resource can be omitted. This means that the resource will be runtime
/// initialized; these runtime initialized resources are also known as *late resources*.
///
/// If this key is omitted its value defaults to an empty list.
///
/// # `init`
///
/// This key is optional. Its value is a set of key values. All the possible keys are shown below:
///
/// ``` text
/// init: {
/// path: ..,
/// }
/// ```
///
/// ## `init.path`
///
/// This key is optional. Its value is a Rust path, like `foo::bar::baz`, that points to the
/// initialization function.
///
/// If the key is omitted its value defaults to `init`.
///
/// ## `init.resources`
///
/// This key is optional. Its value is a set of resources the `init` function *owns*. The resources
/// in this list must be a subset of the resources listed in the top `resources` key. Note that some
/// restrictions apply:
///
/// - The resources in this list can't be late resources.
/// - The resources that appear in this list can't appear in other list like `idle.resources` or
/// `tasks.$TASK.resources`
///
/// If this key is omitted its value is assumed to be an empty list.
///
/// # `idle`
///
/// This key is optional. Its value is a set of key values. All the possible keys are shown below:
///
/// ``` text
/// idle: {
/// path: ..,
/// resources: [..],
/// }
/// ```
///
/// ## `idle.path`
///
/// This key is optional. Its value is a Rust path, like `foo::bar::baz`, that points to the idle
/// loop function.
///
/// If the key is omitted its value defaults to `idle`.
///
/// ## `idle.resources`
///
/// This key is optional. Its value is a list of resources the `idle` loop has access to. The
/// resources in this list must be a subset of the resources listed in the top `resources` key.
///
/// If omitted its value defaults to an empty list.
///
/// # `tasks`
///
/// This key is optional. Its value is a list of tasks. Each task itself is a set of key value pair.
/// The full syntax is shown below:
///
/// ``` text
/// tasks: {
/// $TASK: {
/// enabled: ..,
/// path: ..,
/// priority: ..,
/// resources: [..],
/// },
/// }
/// ```
///
/// If this key is omitted its value is assumed to be an empty list.
///
/// ## `tasks.$TASK`
///
/// The key must be either a Cortex-M exception or a device specific interrupt. `PENDSV`, `SVCALL`,
/// `SYS_TICK` are considered as exceptions. All other names are assumed to be interrupts.
///
/// ## `tasks.$TASK.enabled`
///
/// This key is optional for interrupts and forbidden for exceptions. Its value must be a boolean
/// and indicates whether the interrupt will be enabled (`true`) or disabled (`false`) after `init`
/// ends and before `idle` starts.
///
/// If this key is omitted its value defaults to `true`.
///
/// ## `tasks.$TASK.path`
///
/// The value of this key is a Rust path, like `foo::bar::baz`, that points to the handler of this
/// task.
///
/// ## `tasks.$TASK.priority`
///
/// This key is optional. Its value is an integer with type `u8` that specifies the priority of this
/// task. The minimum valid priority is 1. The maximum valid priority depends on the number of the
/// NVIC priority bits the device has; if the device has 4 priority bits the maximum allowed value
/// would be 16.
///
/// If this key is omitted its value defaults to `1`.
///
/// ## `tasks.$TASK.resources`
///
/// This key is optional. Its value is a list of resources this task has access to. The resources in
/// this list must be a subset of the resources listed in the top `resources` key.
///
/// If omitted its value defaults to an empty list.
#[proc_macro]
pub fn app(ts: TokenStream) -> TokenStream {
match run(ts) {
@ -177,10 +30,10 @@ pub fn app(ts: TokenStream) -> TokenStream {
fn run(ts: TokenStream) -> Result<TokenStream> {
let app = App::parse(ts)?.check()?;
let app = check::app(app)?;
check::app(&app)?;
let ownerships = analyze::app(&app);
let tokens = trans::app(&app, &ownerships);
let ctxt = analyze::app(&app);
let tokens = trans::app(&ctxt, &app);
Ok(tokens.into())
}

File diff suppressed because it is too large Load diff