diff options
Diffstat (limited to 'macros/src/check.rs')
-rw-r--r-- | macros/src/check.rs | 287 |
1 files changed, 187 insertions, 100 deletions
diff --git a/macros/src/check.rs b/macros/src/check.rs index 8ad13f3c..c22a0f1f 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -1,122 +1,209 @@ -use std::{collections::HashSet, iter}; +use std::collections::HashSet; use proc_macro2::Span; -use syn::parse; - -use crate::syntax::App; - -pub fn app(app: &App) -> parse::Result<()> { - // Check that all referenced resources have been declared - for res in app - .idle - .as_ref() - .map(|idle| -> Box<dyn Iterator<Item = _>> { Box::new(idle.args.resources.iter()) }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(&app.init.args.resources) - .chain(app.exceptions.values().flat_map(|e| &e.args.resources)) - .chain(app.interrupts.values().flat_map(|i| &i.args.resources)) - .chain(app.tasks.values().flat_map(|t| &t.args.resources)) +use rtfm_syntax::{ + analyze::Analysis, + ast::{App, CustomArg, HardwareTaskKind}, +}; +use syn::{parse, Path}; + +pub struct Extra<'a> { + pub device: &'a Path, + pub monotonic: Option<&'a Path>, + pub peripherals: Option<u8>, +} + +impl<'a> Extra<'a> { + pub fn monotonic(&self) -> &'a Path { + self.monotonic.expect("UNREACHABLE") + } +} + +pub fn app<'a>(app: &'a App, analysis: &Analysis) -> parse::Result<Extra<'a>> { + // check that all exceptions are valid; only exceptions with configurable priorities are + // accepted + for (name, task) in app + .hardware_tasks + .iter() + .filter(|(_, task)| task.kind == HardwareTaskKind::Exception) { - if !app.resources.contains_key(res) { - return Err(parse::Error::new( - res.span(), - "this resource has NOT been declared", - )); + let name_s = task.args.binds(name).to_string(); + match &*name_s { + // NOTE that some of these don't exist on ARMv6-M but we don't check that here -- the + // code we generate will check that the exception actually exists on ARMv6-M + "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" + | "DebugMonitor" | "PendSV" => {} // OK + + "SysTick" => { + if analysis.timer_queues.get(&task.args.core).is_some() { + return Err(parse::Error::new( + name.span(), + "this exception can't be used because it's being used by the runtime", + )); + } else { + // OK + } + } + + _ => { + return Err(parse::Error::new( + name.span(), + "only exceptions with configurable priority can be used as hardware tasks", + )); + } } } - // Check that late resources have not been assigned to `init` - for res in &app.init.args.resources { - if app.resources.get(res).unwrap().expr.is_none() { - return Err(parse::Error::new( - res.span(), - "late resources can NOT be assigned to `init`", - )); + // check that external (device-specific) interrupts are not named after known (Cortex-M) + // exceptions + for name in app + .extern_interrupts + .iter() + .flat_map(|(_, interrupts)| interrupts.keys()) + { + let name_s = name.to_string(); + + match &*name_s { + "NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault" + | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => { + return Err(parse::Error::new( + name.span(), + "Cortex-M exceptions can't be used as `extern` interrupts", + )); + } + + _ => {} } } - if app.resources.iter().any(|(_, res)| res.expr.is_none()) { - // Check that `init` returns `LateResources` if there's any declared late resource - if !app.init.returns_late_resources { - return Err(parse::Error::new( - app.init.span, - "late resources have been specified so `init` must return `init::LateResources`", - )); - } - } else if app.init.returns_late_resources { - // If there are no late resources the signature should be `fn(init::Context)` - if app.init.returns_late_resources { - return Err(parse::Error::new( - app.init.span, - "`init` signature must be `fn(init::Context)` if there are no late resources", - )); + // check that there are enough external interrupts to dispatch the software tasks and the timer + // queue handler + for core in 0..app.args.cores { + let mut first = None; + let priorities = app + .software_tasks + .iter() + .filter_map(|(name, task)| { + if task.args.core == core { + first = Some(name); + Some(task.args.priority) + } else { + None + } + }) + .chain(analysis.timer_queues.get(&core).map(|tq| tq.priority)) + .collect::<HashSet<_>>(); + + let need = priorities.len(); + let given = app + .extern_interrupts + .get(&core) + .map(|ei| ei.len()) + .unwrap_or(0); + if need > given { + let s = if app.args.cores == 1 { + format!( + "not enough `extern` interrupts to dispatch \ + all software tasks (need: {}; given: {})", + need, given + ) + } else { + format!( + "not enough `extern` interrupts to dispatch \ + all software tasks on this core (need: {}; given: {})", + need, given + ) + }; + + return Err(parse::Error::new(first.unwrap().span(), &s)); } } - // Check that all referenced tasks have been declared - for task in app - .idle - .as_ref() - .map(|idle| -> Box<dyn Iterator<Item = _>> { - Box::new(idle.args.schedule.iter().chain(&idle.args.spawn)) - }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(&app.init.args.schedule) - .chain(&app.init.args.spawn) - .chain( - app.exceptions - .values() - .flat_map(|e| e.args.schedule.iter().chain(&e.args.spawn)), - ) - .chain( - app.interrupts - .values() - .flat_map(|i| i.args.schedule.iter().chain(&i.args.spawn)), - ) - .chain( - app.tasks - .values() - .flat_map(|t| t.args.schedule.iter().chain(&t.args.spawn)), - ) - { - if !app.tasks.contains_key(task) { - return Err(parse::Error::new( - task.span(), - "this task has NOT been declared", - )); + let mut device = None; + let mut monotonic = None; + let mut peripherals = None; + + for (k, v) in &app.args.custom { + let ks = k.to_string(); + + match &*ks { + "device" => match v { + CustomArg::Path(p) => device = Some(p), + + _ => { + return Err(parse::Error::new( + k.span(), + "unexpected argument value; this should be a path", + )); + } + }, + + "monotonic" => match v { + CustomArg::Path(p) => monotonic = Some(p), + + _ => { + return Err(parse::Error::new( + k.span(), + "unexpected argument value; this should be a path", + )); + } + }, + + "peripherals" => match v { + CustomArg::Bool(x) if app.args.cores == 1 => { + peripherals = if *x { Some(0) } else { None } + } + + CustomArg::UInt(x) if app.args.cores != 1 => { + peripherals = if *x < u64::from(app.args.cores) { + Some(*x as u8) + } else { + return Err(parse::Error::new( + k.span(), + &format!( + "unexpected argument value; \ + this should be an integer in the range 0..={}", + app.args.cores + ), + )); + } + } + + _ => { + return Err(parse::Error::new( + k.span(), + if app.args.cores == 1 { + "unexpected argument value; this should be a boolean" + } else { + "unexpected argument value; this should be an integer" + }, + )); + } + }, + + _ => { + return Err(parse::Error::new(k.span(), "unexpected argument")); + } } } - // Check that there are enough free interrupts to dispatch all tasks - let ndispatchers = app - .tasks - .values() - .map(|t| t.args.priority) - .collect::<HashSet<_>>() - .len(); - if ndispatchers > app.free_interrupts.len() { + if !analysis.timer_queues.is_empty() && monotonic.is_none() { return Err(parse::Error::new( Span::call_site(), - &*format!( - "{} free interrupt{} (`extern {{ .. }}`) {} required to dispatch all soft tasks", - ndispatchers, - if ndispatchers > 1 { "s" } else { "" }, - if ndispatchers > 1 { "are" } else { "is" }, - ), + "a `monotonic` timer must be specified to use the `schedule` API", )); } - // Check that free interrupts are not being used - for (handler, interrupt) in &app.interrupts { - let name = interrupt.args.binds(handler); - - if app.free_interrupts.contains_key(name) { - return Err(parse::Error::new( - name.span(), - "free interrupts (`extern { .. }`) can't be used as interrupt handlers", - )); - } + if let Some(device) = device { + Ok(Extra { + device, + monotonic, + peripherals, + }) + } else { + Err(parse::Error::new( + Span::call_site(), + "a `device` argument must be specified in `#[rtfm::app]`", + )) } - - Ok(()) } |