aboutsummaryrefslogtreecommitdiff
path: root/macros/src/check.rs
diff options
context:
space:
mode:
Diffstat (limited to 'macros/src/check.rs')
-rw-r--r--macros/src/check.rs287
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(())
}