From 86a360a3964ecb04a37c0424c76d7b43a9fd40fe Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Tue, 4 Jul 2017 11:26:11 -0500 Subject: [PATCH] rtfm! macro take 2 --- Cargo.toml | 22 +- build.rs | 130 +-- macros/Cargo.toml | 11 + macros/src/check.rs | 17 + macros/src/lib.rs | 63 ++ macros/src/syntax/mod.rs | 52 ++ macros/src/syntax/parse.rs | 496 ++++++++++++ macros/src/trans.rs | 486 +++++++++++ macros/src/util.rs | 48 ++ src/lib.rs | 1110 +++++--------------------- tests/cfail.rs | 16 - tests/cfail/access.rs | 35 - tests/cfail/ceiling.rs | 29 - tests/cfail/lock.rs | 36 - tests/cfail/peripherals-alias-1.rs | 68 -- tests/cfail/peripherals-alias-2.rs | 74 -- tests/cfail/race-1.rs | 42 - tests/cfail/race-2.rs | 43 - tests/cfail/raise.rs | 24 - tests/cfail/tasks-p0.rs | 92 --- tests/cfail/tasks-same-handler.rs | 98 --- tests/cfail/tasks-wrong-idle.rs | 89 --- tests/cfail/tasks-wrong-init.rs | 91 --- tests/cfail/tasks-wrong-priority.rs | 91 --- tests/cfail/tasks-wrong-task.rs | 91 --- tests/cfail/tasks-wrong-threshold.rs | 91 --- tests/cfail/token-transfer.rs | 159 ---- 27 files changed, 1367 insertions(+), 2237 deletions(-) create mode 100644 macros/Cargo.toml create mode 100644 macros/src/check.rs create mode 100644 macros/src/lib.rs create mode 100644 macros/src/syntax/mod.rs create mode 100644 macros/src/syntax/parse.rs create mode 100644 macros/src/trans.rs create mode 100644 macros/src/util.rs delete mode 100644 tests/cfail.rs delete mode 100644 tests/cfail/access.rs delete mode 100644 tests/cfail/ceiling.rs delete mode 100644 tests/cfail/lock.rs delete mode 100644 tests/cfail/peripherals-alias-1.rs delete mode 100644 tests/cfail/peripherals-alias-2.rs delete mode 100644 tests/cfail/race-1.rs delete mode 100644 tests/cfail/race-2.rs delete mode 100644 tests/cfail/raise.rs delete mode 100644 tests/cfail/tasks-p0.rs delete mode 100644 tests/cfail/tasks-same-handler.rs delete mode 100644 tests/cfail/tasks-wrong-idle.rs delete mode 100644 tests/cfail/tasks-wrong-init.rs delete mode 100644 tests/cfail/tasks-wrong-priority.rs delete mode 100644 tests/cfail/tasks-wrong-task.rs delete mode 100644 tests/cfail/tasks-wrong-threshold.rs delete mode 100644 tests/cfail/token-transfer.rs diff --git a/Cargo.toml b/Cargo.toml index e2cd37f31f..f37449d797 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,24 +10,8 @@ keywords = ["arm", "cortex-m"] license = "MIT OR Apache-2.0" name = "cortex-m-rtfm" repository = "https://github.com/japaric/cortex-m-rtfm" -version = "0.1.1" - -[build-dependencies] -quote = "0.3.15" -syn = "0.11.10" +version = "0.2.0" [dependencies] -cortex-m = "0.2.6" -static-ref = "0.1.0" -typenum = "1.7.0" - -[dev-dependencies] -compiletest_rs = "0.2.5" - -[features] -# Number of priority bits -P2 = [] -P3 = [] -P4 = [] -P5 = [] -default = ["P4"] +cortex-m = "0.3.0" +static-ref = "0.2.0" \ No newline at end of file diff --git a/build.rs b/build.rs index 19b1217020..ebfee06ce3 100644 --- a/build.rs +++ b/build.rs @@ -1,137 +1,11 @@ -#[macro_use] -extern crate quote; -extern crate syn; - use std::env; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; - -use syn::{Ident, IntTy, Lit}; fn main() { let target = env::var("TARGET").unwrap(); - if target.starts_with("thumbv6m") { - println!("cargo:rustc-cfg=thumbv6m"); + if target.starts_with("thumbv6m-") { + println!("cargo:rustc-cfg=armv6m"); } - let bits = if env::var_os("CARGO_FEATURE_P2").is_some() { - 2 - } else if env::var_os("CARGO_FEATURE_P3").is_some() { - 3 - } else if env::var_os("CARGO_FEATURE_P4").is_some() { - 4 - } else if env::var_os("CARGO_FEATURE_P5").is_some() { - 5 - } else { - panic!( - "Specify the number of priority bits through one of these Cargo \ - features: P2, P3, P4 or P5" - ); - }; - - let n = Lit::Int(bits, IntTy::Unsuffixed); - let mut tokens = vec![]; - tokens.push( - quote! { - const PRIORITY_BITS: u8 = #n; - }, - ); - - // Ceilings and thresholds - for i in 0..(1 << bits) + 1 { - let c = Ident::new(format!("C{}", i)); - let t = Ident::new(format!("T{}", i)); - let u = Ident::new(format!("U{}", i)); - - let doc = format!("A ceiling of {}", i); - tokens.push( - quote! { - #[doc = #doc] - pub type #c = ::typenum::#u; - }, - ); - - let doc = format!("A preemption threshold of {}", i); - tokens.push( - quote! { - #[doc = #doc] - pub type #t = Threshold<::typenum::#u>; - }, - ); - } - - // Priorities - for i in 0..(1 << bits) + 1 { - let p = Ident::new(format!("P{}", i)); - let u = Ident::new(format!("U{}", i)); - - let doc = format!( - "A priority of {}{}", - i, - if i == 0 { - ", the lowest priority" - } else if i == (1 << bits) { - ", the highest priority" - } else { - "" - } - ); - tokens.push( - quote! { - #[doc = #doc] - pub type #p = Priority<::typenum::#u>; - }, - ); - } - - // GreaterThanOrEqual & LessThanOrEqual - for i in 0..(1 << bits) + 1 { - for j in 0..(i + 1) { - let i = Ident::new(format!("U{}", i)); - let j = Ident::new(format!("U{}", j)); - - tokens.push( - quote! { - unsafe impl GreaterThanOrEqual<::typenum::#j> for - ::typenum::#i {} - - unsafe impl LessThanOrEqual<::typenum::#i> for - ::typenum::#j {} - }, - ); - } - } - - let u = Ident::new(format!("U{}", (1 << bits))); - let c = Ident::new(format!("C{}", (1 << bits))); - let p = Ident::new(format!("P{}", (1 << bits))); - let t = Ident::new(format!("T{}", (1 << bits))); - tokens.push( - quote! { - /// Maximum ceiling - pub type CMax = #c; - - /// Maximum priority - pub type PMax = #p; - - /// Maximum preemption threshold - pub type TMax = #t; - - /// Maximum priority level - pub type UMax = ::typenum::#u; - }, - ); - - let tokens = quote! { - #(#tokens)* - }; - - let out_dir = env::var("OUT_DIR").unwrap(); - let mut out = File::create(PathBuf::from(out_dir).join("prio.rs")).unwrap(); - - out.write_all(tokens.as_str().as_bytes()).unwrap(); - println!("cargo:rerun-if-changed=build.rs"); } diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 0000000000..0e255e38a0 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,11 @@ +[package] +authors = ["Jorge Aparicio "] +name = "rtfm-macros" +version = "0.1.0" + +[dependencies] +quote = "0.3.15" +syn = "0.11.11" + +[lib] +plugin = true diff --git a/macros/src/check.rs b/macros/src/check.rs new file mode 100644 index 0000000000..ddd9abc429 --- /dev/null +++ b/macros/src/check.rs @@ -0,0 +1,17 @@ +use syntax::Resources; +use util::{Ceiling, Ceilings}; + +pub fn resources(resources: &Resources, ceilings: &Ceilings) { + for resource in resources.keys() { + if let Some(ceiling) = ceilings.get(&resource) { + assert_ne!( + *ceiling, + Ceiling::Owned, + "{} should be local data", + resource + ); + } else { + panic!("resource {} is unused", resource) + } + } +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 0000000000..05210fbb64 --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,63 @@ +#![feature(plugin_registrar)] +#![feature(proc_macro_internals)] +#![feature(rustc_private)] +#![recursion_limit = "128"] + +extern crate proc_macro; +#[macro_use] +extern crate quote; +extern crate rustc_errors; +extern crate rustc_plugin; +extern crate syn; +extern crate syntax as rustc_syntax; + +use proc_macro::TokenStream; +use rustc_errors::Handler; +use rustc_errors::emitter::ColorConfig; +use rustc_plugin::Registry; +use rustc_syntax::codemap::{CodeMap, FilePathMapping}; +use rustc_syntax::ext::base::SyntaxExtension; +use rustc_syntax::parse::ParseSess; +use rustc_syntax::symbol::Symbol; +use rustc_syntax::tokenstream::TokenStream as TokenStream_; +use std::rc::Rc; +use std::str::FromStr; + +mod check; +mod syntax; +mod trans; +mod util; + +fn expand_rtfm(ts: TokenStream_) -> TokenStream_ { + let input = format!("{}", ts); + + let app = syntax::parse::app(&input); + let ceilings = util::compute_ceilings(&app); + check::resources(&app.resources, &ceilings); + + let output = format!("{}", trans::app(&app, &ceilings)); + + let mapping = FilePathMapping::empty(); + let codemap = Rc::new(CodeMap::new(mapping)); + + let tty_handler = Handler::with_tty_emitter( + ColorConfig::Auto, + true, + false, + Some(codemap.clone()), + ); + + let sess = ParseSess::with_span_handler(tty_handler, codemap.clone()); + proc_macro::__internal::set_parse_sess(&sess, || { + let ts = TokenStream::from_str(&output).unwrap(); + proc_macro::__internal::token_stream_inner(ts) + }) +} + +#[plugin_registrar] +pub fn plugin_registrar(reg: &mut Registry) { + reg.register_syntax_extension( + Symbol::intern("rtfm"), + SyntaxExtension::ProcMacro(Box::new(expand_rtfm)), + ); +} diff --git a/macros/src/syntax/mod.rs b/macros/src/syntax/mod.rs new file mode 100644 index 0000000000..c856617ea1 --- /dev/null +++ b/macros/src/syntax/mod.rs @@ -0,0 +1,52 @@ +use std::collections::{HashMap, HashSet}; + +use syn::Ident; +use quote::Tokens; + +pub mod parse; + +#[derive(Debug)] +pub struct App { + pub device: Tokens, + pub idle: Idle, + pub init: Init, + pub resources: Resources, + pub tasks: Tasks, +} + +#[derive(Debug)] +pub struct Init { + pub path: Tokens, + pub resources: HashSet, +} + +#[derive(Debug)] +pub struct Idle { + pub local: Resources, + pub path: Tokens, + pub resources: HashSet, +} + +#[derive(Debug)] +pub struct Task { + pub kind: Kind, + pub priority: u8, + pub resources: HashSet, +} + +#[derive(Debug)] +pub enum Kind { + Exception, + Interrupt { enabled: bool }, +} + +// $ident: $ty = $expr; +#[derive(Debug)] +pub struct Resource { + pub expr: Tokens, + pub ty: Tokens, +} + +pub type Resources = HashMap; + +pub type Tasks = HashMap; diff --git a/macros/src/syntax/parse.rs b/macros/src/syntax/parse.rs new file mode 100644 index 0000000000..e6d3f461cd --- /dev/null +++ b/macros/src/syntax/parse.rs @@ -0,0 +1,496 @@ +use std::collections::{HashMap, HashSet}; + +use syn::{self, DelimToken, Ident, IntTy, Lit, Token, TokenTree}; +use quote::Tokens; + +use syntax::{App, Idle, Init, Kind, Resource, Resources, Task, Tasks}; + +pub fn app(input: &str) -> App { + let tts = syn::parse_token_trees(input).unwrap(); + + let mut device = None; + let mut init = None; + let mut idle = None; + let mut resources = None; + let mut tasks = None; + + let mut tts = tts.into_iter(); + while let Some(tt) = tts.next() { + let id = if let TokenTree::Token(Token::Ident(id)) = tt { + id + } else { + panic!("expected ident, found {:?}", tt); + }; + + let tt = tts.next(); + assert_eq!( + tt, + Some(TokenTree::Token(Token::Colon)), + "expected colon, found {:?}", + tt + ); + + match id.as_ref() { + "device" => { + assert!(device.is_none(), "duplicated device field"); + + let mut pieces = vec![]; + + loop { + if let Some(tt) = tts.next() { + if tt == TokenTree::Token(Token::Comma) { + break; + } else { + pieces.push(tt); + } + } else { + panic!("expected path, found EOM"); + } + } + + device = Some(quote!(#(#pieces)*)); + continue; + } + "idle" => { + assert!(idle.is_none(), "duplicated idle field"); + + let tt = tts.next(); + if let Some(TokenTree::Delimited(block)) = tt { + assert_eq!( + block.delim, + DelimToken::Brace, + "expected brace, found {:?}", + block.delim + ); + + idle = Some(super::parse::idle(block.tts)); + } else { + panic!("expected block, found {:?}", tt); + } + } + "init" => { + assert!(init.is_none(), "duplicated init field"); + + let tt = tts.next(); + if let Some(TokenTree::Delimited(block)) = tt { + assert_eq!( + block.delim, + DelimToken::Brace, + "expected brace, found {:?}", + block.delim + ); + + init = Some(super::parse::init(block.tts)); + } else { + panic!("expected block, found {:?}", tt); + } + } + "resources" => { + assert!(resources.is_none(), "duplicated resources field"); + + let tt = tts.next(); + if let Some(TokenTree::Delimited(block)) = tt { + assert_eq!( + block.delim, + DelimToken::Brace, + "expected brace, found {:?}", + block.delim + ); + + resources = Some(super::parse::resources(block.tts)); + } + } + "tasks" => { + assert!(tasks.is_none(), "duplicated tasks field"); + + let tt = tts.next(); + if let Some(TokenTree::Delimited(block)) = tt { + assert_eq!( + block.delim, + DelimToken::Brace, + "expected brace, found {:?}", + block.delim + ); + + tasks = Some(super::parse::tasks(block.tts)); + } + } + id => panic!("unexpected field {}", id), + } + + let tt = tts.next(); + assert_eq!( + tt, + Some(TokenTree::Token(Token::Comma)), + "expected comma, found {:?}", + tt + ); + } + + App { + device: device.expect("device field is missing"), + idle: idle.expect("idle field is missing"), + init: init.expect("init field is missing"), + resources: resources.expect("resources field is missing"), + tasks: tasks.expect("tasks field is missing"), + } +} + +fn idle_init( + tts: Vec, + allows_locals: bool, +) -> (Option, Tokens, HashSet) { + let mut tts = tts.into_iter(); + + let mut local = None; + let mut path = None; + let mut resources = None; + while let Some(tt) = tts.next() { + let id = if let TokenTree::Token(Token::Ident(id)) = tt { + id + } else { + panic!("expected ident, found {:?}", tt); + }; + + let tt = tts.next(); + assert_eq!( + tt, + Some(TokenTree::Token(Token::Colon)), + "expected colon, found {:?}", + tt + ); + + match id.as_ref() { + "local" if allows_locals => { + assert!(local.is_none(), "duplicated local field"); + + let tt = tts.next(); + if let Some(TokenTree::Delimited(block)) = tt { + assert_eq!( + block.delim, + DelimToken::Brace, + "expected brace, found {:?}", + block.delim + ); + + local = Some(super::parse::resources(block.tts)); + } else { + panic!("expected block, found {:?}", tt); + } + } + "path" => { + assert!(path.is_none(), "duplicated path field"); + + let mut pieces = vec![]; + loop { + let tt = tts.next().expect("expected comma, found EOM"); + + if tt == TokenTree::Token(Token::Comma) { + path = Some(quote!(#(#pieces)*)); + break; + } else { + pieces.push(tt); + } + } + + continue; + } + "resources" => { + assert!(resources.is_none(), "duplicated resources field"); + + let tt = tts.next(); + if let Some(TokenTree::Delimited(array)) = tt { + assert_eq!( + array.delim, + DelimToken::Bracket, + "expected bracket, found {:?}", + array.delim + ); + + resources = Some(super::parse::idents(array.tts)); + + } else { + panic!("expected array, found {:?}", tt); + } + } + id => panic!("unexpected field {}", id), + } + + let tt = tts.next(); + assert_eq!( + tt, + Some(TokenTree::Token(Token::Comma)), + "expected comma, found {:?}", + tt + ); + } + + ( + local, + path.expect("path field is missing"), + resources.unwrap_or(HashSet::new()), + ) +} + +pub fn idle(tts: Vec) -> Idle { + let (locals, path, resources) = idle_init(tts, true); + + Idle { + local: locals.expect("local field is missing"), + path, + resources, + } +} + +pub fn init(tts: Vec) -> Init { + let (_, path, resources) = idle_init(tts, false); + + Init { path, resources } +} + +fn idents(tts: Vec) -> HashSet { + let mut idents = HashSet::new(); + + let mut tts = tts.into_iter(); + while let Some(tt) = tts.next() { + if let TokenTree::Token(Token::Ident(id)) = tt { + assert!(!idents.contains(&id), "ident {} already listed", id); + idents.insert(id); + + if let Some(tt) = tts.next() { + assert_eq!(tt, TokenTree::Token(Token::Comma)); + } else { + break; + } + } else { + panic!("expected ident, found {:?}", tt); + }; + } + + idents +} + +pub fn resources(tts: Vec) -> Resources { + let mut resources = HashMap::new(); + + let mut tts = tts.into_iter(); + while let Some(tt) = tts.next() { + let name = if let TokenTree::Token(Token::Ident(ident)) = tt { + ident + } else { + panic!("expected ident, found {:?}", tt); + }; + + assert!( + !resources.contains_key(&name), + "resource {} already listed", + name + ); + + let tt = tts.next(); + assert_eq!( + tt, + Some(TokenTree::Token(Token::Colon)), + "expected comma, found {:?}", + tt + ); + + let mut pieces = vec![]; + loop { + if let Some(tt) = tts.next() { + if tt == TokenTree::Token(Token::Eq) { + break; + } else { + pieces.push(tt); + } + } else { + panic!("expected type, found EOM"); + } + } + + let ty = quote!(#(#pieces)*); + + let mut pieces = vec![]; + loop { + if let Some(tt) = tts.next() { + if tt == TokenTree::Token(Token::Semi) { + break; + } else { + pieces.push(tt); + } + } else { + panic!("expected expression, found EOM"); + } + } + + let expr = quote!(#(#pieces)*); + + let resource = Resource { expr, ty }; + resources.insert(name, resource); + } + + resources +} + +pub fn tasks(tts: Vec) -> Tasks { + let mut tasks = HashMap::new(); + + let mut tts = tts.into_iter(); + while let Some(tt) = tts.next() { + let name = if let TokenTree::Token(Token::Ident(ident)) = tt { + ident + } else { + panic!("expected ident, found {:?}", tt); + }; + + let tt = tts.next(); + assert_eq!( + tt, + Some(TokenTree::Token(Token::Colon)), + "expected colon, found {:?}", + tt + ); + + let tt = tts.next(); + if let Some(TokenTree::Delimited(block)) = tt { + assert_eq!( + block.delim, + DelimToken::Brace, + "expected brace, found {:?}", + block.delim + ); + + assert!(!tasks.contains_key(&name), "task {} already listed", name); + tasks.insert(name, super::parse::task(block.tts)); + } else { + panic!("expected block, found {:?}", tt); + } + + let tt = tts.next(); + assert_eq!( + tt, + Some(TokenTree::Token(Token::Comma)), + "expected comma, found {:?}", + tt + ); + } + + tasks +} + +/// Parses the body of a task +/// +/// ``` +/// enabled: true, +/// priority: 1, +/// resources: [R1, TIM2], +/// ``` +/// +/// the `enabled` field is optional and distinguishes interrupts from +/// exceptions. Interrupts have an `enabled` field, whereas exceptions don't. +fn task(tts: Vec) -> Task { + let mut enabled = None; + let mut priority = None; + let mut resources = None; + + let mut tts = tts.into_iter(); + while let Some(tt) = tts.next() { + let ident = if let TokenTree::Token(Token::Ident(ident)) = tt { + ident + } else { + panic!("expected ident, found {:?}", tt); + }; + + let tt = tts.next(); + assert_eq!( + tt, + Some(TokenTree::Token(Token::Colon)), + "expected colon, found {:?}", + tt + ); + + match ident.as_ref() { + "enabled" => { + assert!(enabled.is_none(), "duplicated enabled field"); + + let tt = tts.next(); + + if let Some(TokenTree::Token(Token::Literal(lit))) = tt { + if let Lit::Bool(b) = lit { + enabled = Some(b); + } else { + panic!("`enabled` value must be a boolean"); + } + } else { + panic!("expected literal, found {:?}", tt); + } + } + "priority" => { + assert!(priority.is_none(), "duplicated priority field"); + + let tt = tts.next(); + + if let Some(TokenTree::Token(Token::Literal(lit))) = tt { + if let Lit::Int(val, ty) = lit { + assert_eq!( + ty, + IntTy::Unsuffixed, + "`priority` value must be an unsuffixed value" + ); + + assert!( + val < 256, + "`priority` value must be less than 256" + ); + + priority = Some(val as u8); + } else { + panic!("enabled value must be a boolean"); + } + } else { + panic!("expected literal, found {:?}", tt); + } + } + "resources" => { + assert!(resources.is_none(), "duplicated resources field"); + + let tt = tts.next(); + if let Some(TokenTree::Delimited(block)) = tt { + assert_eq!( + block.delim, + DelimToken::Bracket, + "expected bracket, found {:?}", + block.delim + ); + + resources = Some(super::parse::idents(block.tts)); + } else { + panic!("expected block, found {:?}", tt); + } + } + id => panic!("unexpected field {}", id), + } + + let tt = tts.next(); + assert_eq!( + tt, + Some(TokenTree::Token(Token::Comma)), + "expected comma, found {:?}", + tt + ); + } + + let resources = resources.expect("resources field is missing"); + let priority = priority.expect("priority field is missing"); + let kind = if let Some(enabled) = enabled { + Kind::Interrupt { enabled } + } else { + Kind::Exception + }; + + Task { + kind, + priority, + resources, + } +} diff --git a/macros/src/trans.rs b/macros/src/trans.rs new file mode 100644 index 0000000000..6cf3a51750 --- /dev/null +++ b/macros/src/trans.rs @@ -0,0 +1,486 @@ +use quote::Tokens; +use syn::Ident; + +use syntax::{App, Kind}; +use util::{Ceiling, Ceilings}; + +fn krate() -> Ident { + Ident::new("rtfm") +} + +pub fn app(app: &App, ceilings: &Ceilings) -> Tokens { + let mut main = vec![]; + let mut root = vec![]; + + super::trans::init(app, &mut main, &mut root); + super::trans::idle(app, ceilings, &mut main, &mut root); + super::trans::resources(app, ceilings, &mut root); + super::trans::tasks(app, ceilings, &mut root); + + root.push(quote! { + fn main() { + #(#main)* + } + }); + + quote!(#(#root)*) +} + +fn init(app: &App, main: &mut Vec, root: &mut Vec) { + let device = &app.device; + let krate = krate(); + + let mut fields = vec![]; + let mut exprs = vec![]; + let mut lifetime = None; + for name in &app.init.resources { + lifetime = Some(quote!('a)); + + if let Some(resource) = app.resources.get(name) { + let ty = &resource.ty; + + fields.push(quote! { + pub #name: &'a mut #ty, + }); + + exprs.push(quote! { + #name: &mut *super::#name.get(), + }); + } else { + fields.push(quote! { + pub #name: &'a mut ::#device::#name, + }); + + exprs.push(quote! { + #name: &mut *::#device::#name.get(), + }); + } + } + + root.push(quote! { + mod init { + #[allow(non_snake_case)] + pub struct Resources<#lifetime> { + #(#fields)* + } + + impl<#lifetime> Resources<#lifetime> { + pub unsafe fn new() -> Self { + Resources { + #(#exprs)* + } + } + } + } + }); + + let mut exceptions = vec![]; + let mut interrupts = vec![]; + for (name, task) in &app.tasks { + match task.kind { + Kind::Exception => { + if exceptions.is_empty() { + exceptions.push(quote! { + let scb = #device::SCB.borrow(cs); + }); + } + + let priority = task.priority; + exceptions.push(quote! { + let prio_bits = #device::NVIC_PRIO_BITS; + let hw = ((1 << prio_bits) - #priority) << (8 - prio_bits); + scb.shpr[rtfm::Exception::#name.nr() - 4].write(hw); + }); + } + Kind::Interrupt { enabled } => { + if interrupts.is_empty() { + interrupts.push(quote! { + let nvic = #device::NVIC.borrow(cs); + }); + } + + let priority = task.priority; + interrupts.push(quote! { + let prio_bits = #device::NVIC_PRIO_BITS; + let hw = ((1 << prio_bits) - #priority) << (8 - prio_bits); + nvic.set_priority(#device::Interrupt::#name, hw); + }); + + if enabled { + interrupts.push(quote! { + nvic.enable(#device::Interrupt::#name); + }); + } else { + interrupts.push(quote! { + nvic.disable(#device::Interrupt::#name); + }); + } + } + } + } + + let init = &app.init.path; + main.push(quote! { + // type check + let init: fn(init::Resources) = #init; + + #krate::atomic(|cs| unsafe { + init(init::Resources::new()); + + #(#exceptions)* + #(#interrupts)* + }); + }); +} + +fn idle( + app: &App, + ceilings: &Ceilings, + main: &mut Vec, + root: &mut Vec, +) { + let krate = krate(); + + let mut mod_items = vec![]; + let mut tys = vec![]; + let mut exprs = vec![]; + + if !app.idle.resources.is_empty() && + !app.idle + .resources + .iter() + .all(|resource| ceilings[resource].is_owned()) + { + tys.push(quote!(#krate::Threshold)); + exprs.push(quote!(unsafe { #krate::Threshold::new(0) })); + } + + if !app.idle.local.is_empty() { + let mut lexprs = vec![]; + let mut lfields = vec![]; + + for (name, resource) in &app.idle.local { + let expr = &resource.expr; + let ty = &resource.ty; + + lfields.push(quote! { + pub #name: #ty, + }); + + lexprs.push(quote! { + #name: #expr, + }); + } + + mod_items.push(quote! { + pub struct Local { + #(#lfields)* + } + }); + + tys.push(quote!(&'static mut idle::Local)); + exprs.push(quote!(unsafe { &mut LOCAL })); + + main.push(quote! { + static mut LOCAL: idle::Local = idle::Local { + #(#lexprs)* + }; + }); + } + + if !app.idle.resources.is_empty() { + let device = &app.device; + let mut lifetime = None; + + let mut rexprs = vec![]; + let mut rfields = vec![]; + for name in &app.idle.resources { + if ceilings[name].is_owned() { + lifetime = Some(quote!('a)); + + rfields.push(quote! { + pub #name: &'a mut ::#device::#name, + }); + + rexprs.push(quote! { + #name: &mut *::#device::#name.get(), + }); + } else { + rfields.push(quote! { + pub #name: super::_resource::#name, + }); + + rexprs.push(quote! { + #name: super::_resource::#name::new(), + }); + } + } + + mod_items.push(quote! { + #[allow(non_snake_case)] + pub struct Resources<#lifetime> { + #(#rfields)* + } + + impl<#lifetime> Resources<#lifetime> { + pub unsafe fn new() -> Self { + Resources { + #(#rexprs)* + } + } + } + }); + + tys.push(quote!(idle::Resources)); + exprs.push(quote!(unsafe { idle::Resources::new() })); + } + + root.push(quote! { + mod idle { + #(#mod_items)* + } + }); + + let idle = &app.idle.path; + main.push(quote! { + // type check + let idle: fn(#(#tys),*) -> ! = #idle; + + idle(#(#exprs),*); + }); +} + +fn tasks(app: &App, ceilings: &Ceilings, root: &mut Vec) { + let krate = krate(); + + for (name, task) in &app.tasks { + let mut exprs = vec![]; + let mut fields = vec![]; + let mut items = vec![]; + + let device = &app.device; + let mut lifetime = None; + for name in &task.resources { + match ceilings[name] { + Ceiling::Shared(ceiling) if ceiling > task.priority => { + fields.push(quote! { + pub #name: super::_resource::#name, + }); + + exprs.push(quote! { + #name: { + super::_resource::#name::new() + }, + }); + } + _ => { + lifetime = Some(quote!('a)); + if let Some(resource) = app.resources.get(name) { + let ty = &resource.ty; + + fields.push(quote! { + pub #name: &'a mut ::#krate::Static<#ty>, + }); + + exprs.push(quote! { + #name: ::#krate::Static::ref_mut( + &mut *super::#name.get(), + ), + }); + } else { + fields.push(quote! { + pub #name: &'a mut ::#device::#name, + }); + + exprs.push(quote! { + #name: &mut *::#device::#name.get(), + }); + } + } + } + } + + items.push(quote! { + #[allow(non_snake_case)] + pub struct Resources<#lifetime> { + #(#fields)* + } + }); + + items.push(quote! { + impl<#lifetime> Resources<#lifetime> { + pub unsafe fn new() -> Self { + Resources { + #(#exprs)* + } + } + } + }); + + let priority = task.priority; + root.push(quote!{ + #[allow(dead_code)] + #[allow(non_snake_case)] + mod #name { + #[deny(dead_code)] + pub const #name: u8 = #priority; + #[deny(const_err)] + const CHECK_PRIORITY: (u8, u8) = ( + #priority - 1, + (1 << ::#device::NVIC_PRIO_BITS) - #priority, + ); + + #(#items)* + } + }); + + } +} + +fn resources(app: &App, ceilings: &Ceilings, root: &mut Vec) { + let krate = krate(); + let device = &app.device; + + let mut items = vec![]; + let mut impls = vec![]; + for (name, ceiling) in ceilings { + let mut impl_items = vec![]; + + match *ceiling { + Ceiling::Owned => continue, + Ceiling::Shared(ceiling) => { + if let Some(resource) = app.resources.get(name) { + let expr = &resource.expr; + let ty = &resource.ty; + + root.push(quote! { + static #name: #krate::Resource<#ty> = + #krate::Resource::new(#expr); + }); + + impl_items.push(quote! { + pub fn borrow<'cs>( + &'cs self, + _cs: &'cs #krate::CriticalSection, + ) -> &'cs #krate::Static<#ty> { + unsafe { + #krate::Static::ref_(&*#name.get()) + } + } + + pub fn borrow_mut<'cs>( + &'cs mut self, + _cs: &'cs #krate::CriticalSection, + ) -> &'cs mut #krate::Static<#ty> { + unsafe { + #krate::Static::ref_mut(&mut *#name.get()) + } + } + + pub fn claim( + &self, + t: &mut #krate::Threshold, + f: F, + ) -> R + where + F: FnOnce( + &#krate::Static<#ty>, + &mut #krate::Threshold) -> R + { + unsafe { + #name.claim( + #ceiling, + #device::NVIC_PRIO_BITS, + t, + f, + ) + } + } + + pub fn claim_mut( + &mut self, + t: &mut #krate::Threshold, + f: F, + ) -> R + where + F: FnOnce( + &mut #krate::Static<#ty>, + &mut #krate::Threshold) -> R + { + unsafe { + #name.claim_mut( + #ceiling, + #device::NVIC_PRIO_BITS, + t, + f, + ) + } + } + }); + } else { + root.push(quote! { + static #name: #krate::Peripheral<#device::#name> = + #krate::Peripheral::new(#device::#name); + }); + + impl_items.push(quote! { + pub fn borrow<'cs>( + &'cs self, + _cs: &'cs #krate::CriticalSection, + ) -> &'cs #device::#name { + unsafe { + &*#name.get() + } + } + + pub fn claim( + &self, + t: &mut #krate::Threshold, + f: F, + ) -> R + where + F: FnOnce( + &#device::#name, + &mut #krate::Threshold) -> R + { + unsafe { + #name.claim( + #ceiling, + #device::NVIC_PRIO_BITS, + t, + f, + ) + } + } + }); + } + + impls.push(quote! { + #[allow(dead_code)] + impl _resource::#name { + #(#impl_items)* + } + }); + + items.push(quote! { + #[allow(non_camel_case_types)] + pub struct #name { _0: () } + + impl #name { + pub unsafe fn new() -> Self { + #name { _0: () } + } + } + }); + } + } + } + + root.push(quote! { + mod _resource { + #(#items)* + } + + #(#impls)* + }); +} diff --git a/macros/src/util.rs b/macros/src/util.rs new file mode 100644 index 0000000000..45f1feeff0 --- /dev/null +++ b/macros/src/util.rs @@ -0,0 +1,48 @@ +use std::collections::HashMap; + +use syn::Ident; + +use syntax::App; + +pub type Ceilings = HashMap; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Ceiling { + Owned, + Shared(u8), +} + +impl Ceiling { + pub fn is_owned(&self) -> bool { + *self == Ceiling::Owned + } +} + +pub fn compute_ceilings(app: &App) -> Ceilings { + let mut ceilings = HashMap::new(); + + for resource in &app.idle.resources { + ceilings.insert(resource.clone(), Ceiling::Owned); + } + + for task in app.tasks.values() { + for resource in &task.resources { + if let Some(ceiling) = ceilings.get_mut(resource) { + match *ceiling { + Ceiling::Owned => *ceiling = Ceiling::Shared(task.priority), + Ceiling::Shared(old) => { + if task.priority > old { + *ceiling = Ceiling::Shared(task.priority); + } + } + } + + continue; + } + + ceilings.insert(resource.clone(), Ceiling::Owned); + } + } + + ceilings +} diff --git a/src/lib.rs b/src/lib.rs index e325d27f08..f5c8b992b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,422 +1,3 @@ -//! Real Time For the Masses (RTFM), a framework for building concurrent -//! applications, for ARM Cortex-M microcontrollers -//! -//! This crate is based on [the RTFM framework] created by the Embedded Systems -//! group at [Luleå University of Technology][ltu], led by Prof. Per Lindgren, -//! and uses a simplified version of the Stack Resource Policy as scheduling -//! policy (check the [references] for details). -//! -//! [the RTFM framework]: http://www.rtfm-lang.org/ -//! [ltu]: https://www.ltu.se/?l=en -//! [per]: https://www.ltu.se/staff/p/pln-1.11258?l=en -//! [references]: ./index.html#references -//! -//! # Features -//! -//! - **Event triggered tasks** as the unit of concurrency. -//! - Support for prioritization of tasks and, thus, **preemptive -//! multitasking**. -//! - **Efficient and data race free memory sharing** through fine grained *non -//! global* critical sections. -//! - **Deadlock free execution** guaranteed at compile time. -//! - **Minimal scheduling overhead** as the scheduler has no "software -//! component": the hardware does all the scheduling. -//! - **Highly efficient memory usage**: All the tasks share a single call stack -//! and there's no hard dependency on a dynamic memory allocator. -//! - **All Cortex M3, M4 and M7 devices are fully supported**. M0(+) is -//! partially supported as the whole API is not available due to missing -//! hardware features. -//! - The number of task priority levels is configurable at compile time through -//! the `P2` (4 levels), `P3` (8 levels), etc. Cargo features. The number of -//! priority levels supported by the hardware is device specific but this -//! crate defaults to 16 as that's the most common scenario. -//! - This task model is amenable to known WCET (Worst Case Execution Time) -//! analysis and scheduling analysis techniques. (Though we haven't yet -//! developed Rust friendly tooling for that.) -//! -//! # Requirements -//! -//! - Tasks must run to completion. That's it, tasks can't contain endless -//! loops. -//! - Task priorities must remain constant at runtime. -//! -//! # Dependencies -//! -//! - A device crate generated using [`svd2rust`] v0.7.x -//! - A `start` lang time: Vanilla `main` must be supported in binary crates. -//! You can use the [`cortex-m-rt`] crate to fulfill the requirement -//! -//! [`svd2rust`]: https://docs.rs/svd2rust/0.7.0/svd2rust/ -//! [`cortex-m-rt`]: https://docs.rs/cortex-m-rt/0.1.1/cortex_m_rt/ -//! -//! # Examples -//! -//! Ordered in increasing level of complexity: -//! -//! - [Zero tasks](./index.html#zero-tasks) -//! - [One task](./index.html#one-task) -//! - [Two "serial" tasks](./index.html#two-serial-tasks) -//! - [Preemptive multitasking](./index.html#preemptive-multitasking) -//! - [Peripherals as resources](./index.html#peripherals-as-resources) -//! -//! ## Zero tasks -//! -//! ``` ignore -//! #![feature(used)] -//! #![no_std] -//! -//! #[macro_use] // for the `hprintln!` macro -//! extern crate cortex_m; -//! -//! // before main initialization + `start` lang item -//! extern crate cortex_m_rt; -//! -//! #[macro_use] // for the `tasks!` macro -//! extern crate cortex_m_rtfm as rtfm; -//! -//! // device crate generated using svd2rust -//! extern crate stm32f30x; -//! -//! use rtfm::{P0, T0, TMax}; -//! -//! // TASKS (None in this example) -//! tasks!(stm32f30x, {}); -//! -//! // INITIALIZATION PHASE -//! fn init(_priority: P0, _threshold: &TMax) { -//! hprintln!("INIT"); -//! } -//! -//! // IDLE LOOP -//! fn idle(_priority: P0, _threshold: T0) -> ! { -//! hprintln!("IDLE"); -//! -//! // Sleep -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! ``` -//! -//! Expected output: -//! -//! ``` text -//! INIT -//! IDLE -//! ``` -//! -//! The `tasks!` macro overrides the `main` function and imposes the following -//! structure into your program: -//! -//! - `init`, the initialization phase, runs first. This function is executed -//! "atomically", in the sense that no task / interrupt can preempt it. -//! -//! - `idle`, a never ending function that runs after `init`. -//! -//! Both `init` and `idle` have a priority of 0, the lowest priority. In RTFM, -//! a higher priority value means more urgent. -//! -//! # One task -//! -//! ``` ignore -//! #![feature(const_fn)] -//! #![feature(used)] -//! #![no_std] -//! -//! extern crate cortex_m_rt; -//! #[macro_use] -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f30x; -//! -//! use stm32f30x::interrupt::Tim7; -//! use rtfm::{Local, P0, P1, T0, T1, TMax}; -//! -//! // INITIALIZATION PHASE -//! fn init(_priority: P0, _threshold: &TMax) { -//! // Configure TIM7 for periodic interrupts -//! // Configure GPIO for LED driving -//! } -//! -//! // IDLE LOOP -//! fn idle(_priority: P0, _threshold: T0) -> ! { -//! // Sleep -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! // TASKS -//! tasks!(stm32f30x, { -//! periodic: Task { -//! interrupt: Tim7, -//! priority: P1, -//! enabled: true, -//! }, -//! }); -//! -//! fn periodic(mut task: Tim7, _priority: P1, _threshold: T1) { -//! // Task local data -//! static STATE: Local = Local::new(false); -//! -//! let state = STATE.borrow_mut(&mut task); -//! -//! // Toggle state -//! *state = !*state; -//! -//! // Blink an LED -//! if *state { -//! LED.on(); -//! } else { -//! LED.off(); -//! } -//! } -//! ``` -//! -//! Here we define a task named `periodic` and bind it to the `Tim7` -//! interrupt. The `periodic` task will run every time the `Tim7` interrupt -//! is triggered. We assign to this task a priority of 1 (`P1`); this is the -//! lowest priority that a task can have. -//! -//! We use the [`Local`](./struct.Local.html) abstraction to add state to the -//! task; this task local data will be preserved across runs of the `periodic` -//! task. Note that `STATE` is owned by the `periodic` task, in the sense that -//! no other task can access it; this is reflected in its type signature (the -//! `Tim7` type parameter). -//! -//! # Two "serial" tasks -//! -//! ``` ignore -//! #![feature(const_fn)] -//! #![feature(used)] -//! #![no_std] -//! -//! extern crate cortex_m_rt; -//! #[macro_use] -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f30x; -//! -//! use core::cell::Cell; -//! -//! use stm32f30x::interrupt::{Tim6Dacunder, Tim7}; -//! use rtfm::{C1, P0, P1, Resource, T0, T1, TMax}; -//! -//! tasks!(stm32f30x, { -//! t1: Task { -//! interrupt: Tim6Dacunder, -//! priority: P1, -//! enabled: true, -//! }, -//! t2: Task { -//! interrupt: Tim7, -//! priority: P1, -//! enabled: true, -//! }, -//! }); -//! -//! // Data shared between tasks `t1` and `t2` -//! static COUNTER: Resource, C1> = Resource::new(Cell::new(0)); -//! -//! fn init(priority: P0, threshold: &TMax) { -//! // .. -//! } -//! -//! fn idle(priority: P0, threshold: T0) -> ! { -//! // Sleep -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! fn t1(_task: Tim6Dacunder, priority: P1, threshold: T1) { -//! let counter = COUNTER.access(&priority, &threshold); -//! -//! counter.set(counter.get() + 1); -//! } -//! -//! fn t2(_task: Tim7, priority: P1, threshold: T1) { -//! let counter = COUNTER.access(&priority, &threshold); -//! -//! counter.set(counter.get() + 2); -//! } -//! ``` -//! -//! Here we declare two tasks, `t1` and `t2`; both with a priority of 1 (`P1`). -//! As both tasks have the same priority, we say that they are *serial* tasks in -//! the sense that `t1` can only run *after* `t2` is done and vice versa; i.e. -//! no preemption between them is possible. -//! -//! To share data between these two tasks, we use the -//! [`Resource`](./struct.Resource.html) abstraction. As the tasks can't preempt -//! each other, they can access the `COUNTER` resource using the zero cost -//! [`access`](./struct.Resource.html#method.access) method -- no -//! synchronization is required. -//! -//! `COUNTER` has an extra type parameter: `C1`. This is the *ceiling* of the -//! resource. For now suffices to say that the ceiling must be the maximum of -//! the priorities of all the tasks that access the resource -- in this case, -//! `C1 == max(P1, P1)`. If you try a smaller value like `C0`, you'll find out -//! that your program doesn't compile. -//! -//! # Preemptive multitasking -//! -//! ``` ignore -//! #![feature(const_fn)] -//! #![feature(used)] -//! #![no_std] -//! -//! extern crate cortex_m_rt; -//! #[macro_use] -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f30x; -//! -//! use core::cell::Cell; -//! -//! use stm32f30x::interrupt::{Tim6Dacunder, Tim7}; -//! use rtfm::{C2, P0, P1, P2, Resource, T0, T1, T2, TMax}; -//! -//! tasks!(stm32f30x, { -//! t1: Task { -//! interrupt: Tim6Dacunder, -//! priority: P1, -//! enabled: true, -//! }, -//! t2: Task { -//! interrupt: Tim7, -//! priority: P2, -//! enabled: true, -//! }, -//! }); -//! -//! static COUNTER: Resource, C2> = Resource::new(Cell::new(0)); -//! -//! fn init(priority: P0, threshold: &TMax) { -//! // .. -//! } -//! -//! fn idle(priority: P0, threshold: T0) -> ! { -//! // Sleep -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! fn t1(_task: Tim6Dacunder, priority: P1, threshold: T1) { -//! // .. -//! -//! threshold.raise( -//! &COUNTER, |threshold: &T2| { -//! let counter = COUNTER.access(&priority, threshold); -//! -//! counter.set(counter.get() + 1); -//! } -//! ); -//! -//! // .. -//! } -//! -//! fn t2(_task: Tim7, priority: P2, threshold: T2) { -//! let counter = COUNTER.access(&priority, &threshold); -//! -//! counter.set(counter.get() + 2); -//! } -//! ``` -//! -//! Now we have a variation of the previous example. Like before, `t1` has a -//! priority of 1 (`P1`) but `t2` now has a priority of 2 (`P2`). This means -//! that `t2` can preempt `t1` if a `Tim7` interrupt occurs while `t1` is -//! being executed. -//! -//! To avoid data races, `t1` must modify `COUNTER` in an atomic way; i.e. `t2` -//! most not preempt `t1` while `COUNTER` is being modified. This is -//! accomplished by [`raise`](./struct.C.html#method.raise)-ing the preemption -//! `threshold`. This creates a critical section, denoted by a closure; for -//! whose execution, `COUNTER` is accessible while `t2` is prevented from -//! preempting `t1`. -//! -//! How `t2` accesses `COUNTER` remains unchanged. Since `t1` can't preempt `t2` -//! due to the differences in priority; no critical section is needed in `t2`. -//! -//! Note that the ceiling of `COUNTER` had to be changed to `C2`. This is -//! required because the ceiling must be the maximum between `P1` and `P2`. -//! -//! Finally, it should be noted that the critical section in `t1` will only -//! block tasks with a priority of 2 or lower. This is exactly what the -//! preemption threshold represents: it's the "bar" that a task priority must -//! pass in order to be able to preempt the current task / critical section. -//! Note that a task with a priority of e.g. 3 (`P3`) effectively imposes a -//! threshold of 3 (`C3`) because only a task with a priority of 4 or greater -//! can preempt it. -//! -//! # Peripherals as resources -//! -//! ``` ignore -//! #![feature(const_fn)] -//! #![feature(used)] -//! #![no_std] -//! -//! extern crate cortex_m_rt; -//! #[macro_use] -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f30x; -//! -//! use rtfm::{P0, Peripheral, T0, TMax}; -//! -//! peripherals!(stm32f30x, { -//! GPIOA: Peripheral { -//! register_block: Gpioa, -//! ceiling: C0, -//! }, -//! RCC: Peripheral { -//! register_block: Rcc, -//! ceiling: C0, -//! }, -//! }); -//! -//! tasks!(stm32f30x, {}); -//! -//! fn init(priority: P0, threshold: &TMax) { -//! let gpioa = GPIOA.access(&priority, threshold); -//! let rcc = RCC.access(&priority, threshold); -//! -//! // .. -//! } -//! -//! fn idle(_priority: P0, _threshold: T0) -> ! { -//! // Sleep -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! ``` -//! -//! Peripherals are global resources too and as such they can be protected in -//! the same way as `Resource`s using the -//! [`Peripheral`](./struct.Peripheral.html) abstraction. -//! -//! `Peripheral` and `Resource` has pretty much the same API except that -//! `Peripheral` instances must be declared using the -//! [`peripherals!`](./macro.peripherals.html) macro. -//! -//! # References -//! -//! - Baker, T. P. (1991). Stack-based scheduling of realtime processes. -//! *Real-Time Systems*, 3(1), 67-99. -//! -//! > The original Stack Resource Policy paper. [PDF][srp]. -//! -//! [srp]: http://www.cs.fsu.edu/~baker/papers/mstacks3.pdf -//! -//! - Eriksson, J., Häggström, F., Aittamaa, S., Kruglyak, A., & Lindgren, P. -//! (2013, June). Real-time for the masses, step 1: Programming API and static -//! priority SRP kernel primitives. In Industrial Embedded Systems (SIES), -//! 2013 8th IEEE International Symposium on (pp. 110-113). IEEE. -//! -//! > A description of the RTFM task and resource model. [PDF][rtfm] -//! -//! [rtfm]: http://www.diva-portal.org/smash/get/diva2:1005680/FULLTEXT01.pdf - -#![deny(missing_docs)] -#![deny(warnings)] #![feature(asm)] #![feature(const_fn)] #![feature(optin_builtin_traits)] @@ -424,550 +5,237 @@ extern crate cortex_m; extern crate static_ref; -extern crate typenum; use core::cell::UnsafeCell; -use core::marker::PhantomData; -use core::ptr; - -use cortex_m::ctxt::Context; -use cortex_m::interrupt::Nr; -#[cfg(not(thumbv6m))] -use cortex_m::register::{basepri, basepri_max}; -use static_ref::Ref; -use typenum::{Cmp, Greater, U0, Unsigned}; -#[cfg(not(thumbv6m))] -use typenum::Less; pub use cortex_m::asm::{bkpt, wfi}; +pub use cortex_m::interrupt::CriticalSection; +pub use cortex_m::interrupt::free as atomic; +pub use static_ref::Static; +#[cfg(not(armv6m))] +use cortex_m::register::{basepri_max, basepri}; -#[doc(hidden)] -pub use cortex_m::peripheral::NVIC as _NVIC; - -/// Compiler barrier -#[cfg(not(thumbv6m))] +#[cfg(not(armv6m))] macro_rules! barrier { () => { - asm!("" - : - : - : "memory" - : "volatile"); + asm!("" ::: "memory" : "volatile"); } } -/// Task local data -/// -/// This data can only be accessed by the task `T` -pub struct Local { - _task: PhantomData, - data: UnsafeCell, -} - -impl Local { - /// Creates a task local variable with some initial `value` - pub const fn new(value: T) -> Self { - Local { - _task: PhantomData, - data: UnsafeCell::new(value), +#[inline(always)] +unsafe fn claim( + data: T, + ceiling: u8, + nvic_prio_bits: u8, + t: &mut Threshold, + f: F, + g: G, +) -> R +where + F: FnOnce(U, &mut Threshold) -> R, + G: FnOnce(T) -> U, +{ + let max_priority = 1 << nvic_prio_bits; + if ceiling > t.0 { + match () { + #[cfg(armv6m)] + () => { + atomic(|_| f(g(data), &mut Threshold::new(max_priority))) + } + #[cfg(not(armv6m))] + () => { + if ceiling == max_priority { + atomic(|_| f(g(data), &mut Threshold::new(max_priority))) + } else { + let old = basepri::read(); + let hw = (max_priority - ceiling) << (8 - nvic_prio_bits); + basepri_max::write(hw); + barrier!(); + let ret = f(g(data), &mut Threshold(ceiling)); + barrier!(); + basepri::write(old); + ret + } + } } - } - - /// Borrows the task local data for the duration of the task - pub fn borrow<'task>(&'static self, _task: &'task TASK) -> &'task T { - unsafe { &*self.data.get() } - } - - /// Mutably borrows the task local data for the duration of the task - pub fn borrow_mut<'task>( - &'static self, - _task: &'task mut TASK, - ) -> &'task mut T { - unsafe { &mut *self.data.get() } + } else { + f(g(data), t) } } -unsafe impl Sync for Local {} +pub struct Peripheral

+where + P: 'static, +{ + // FIXME(rustc/LLVM bug?) storing the ceiling in the resource de-optimizes + // claims (the ceiling value gets loaded at runtime rather than inlined) + // ceiling: u8, + peripheral: cortex_m::peripheral::Peripheral

, +} -/// A resource with ceiling `C` -/// -/// A resource is used to share memory between two or more tasks -pub struct Resource { - _ceiling: PhantomData, +impl

Peripheral

{ + pub const fn new(peripheral: cortex_m::peripheral::Peripheral

) -> Self { + Peripheral { peripheral } + } + + #[inline(always)] + pub unsafe fn claim( + &'static self, + ceiling: u8, + nvic_prio_bits: u8, + t: &mut Threshold, + f: F, + ) -> R + where + F: FnOnce(&P, &mut Threshold) -> R, + { + claim( + &self.peripheral, + ceiling, + nvic_prio_bits, + t, + f, + |peripheral| &*peripheral.get(), + ) + } + + pub fn get(&self) -> *mut P { + self.peripheral.get() + } +} + +unsafe impl

Sync for Peripheral

+where + P: Send, +{ +} + +pub struct Resource { + // FIXME(rustc/LLVM bug?) storing the ceiling in the resource de-optimizes + // claims (the ceiling value gets loaded at runtime rather than inlined) + // ceiling: u8, data: UnsafeCell, } -impl Resource -where - RC: GreaterThanOrEqual, - RC: LessThanOrEqual, -{ - /// Creates a new resource - pub const fn new(data: T) -> Self { - Resource { - _ceiling: PhantomData, - data: UnsafeCell::new(data), - } +impl Resource { + pub const fn new(value: T) -> Self { + Resource { data: UnsafeCell::new(value) } } -} -impl Resource { - /// Grants data race free and deadlock free access to the resource data - /// - /// This operation is zero cost and doesn't impose any additional blocking. - /// - /// # Requirements - /// - /// To access the resource data these conditions must be met: - /// - /// - The resource ceiling must be greater than or equal to the task - /// priority - /// - The preemption threshold must be greater than or equal to the resource - /// ceiling - pub fn access<'cs, TP, PT>( + #[inline(always)] + pub unsafe fn claim( &'static self, - _task_priority: &Priority, - _preemption_threshold: &'cs Threshold, - ) -> Ref<'cs, T> + ceiling: u8, + nvic_prio_bits: u8, + t: &mut Threshold, + f: F, + ) -> R where - RC: GreaterThanOrEqual, - PT: GreaterThanOrEqual, + F: FnOnce(&Static, &mut Threshold) -> R, { - unsafe { Ref::new(&*self.data.get()) } + claim(&self.data, ceiling, nvic_prio_bits, t, f, |data| { + Static::ref_(&*data.get()) + }) + } + + #[inline(always)] + pub unsafe fn claim_mut( + &'static self, + ceiling: u8, + nvic_prio_bits: u8, + t: &mut Threshold, + f: F, + ) -> R + where + F: FnOnce(&mut Static, &mut Threshold) -> R, + { + claim(&self.data, ceiling, nvic_prio_bits, t, f, |data| { + Static::ref_mut(&mut *data.get()) + }) + } + + pub fn get(&self) -> *mut T { + self.data.get() } } -unsafe impl Sync for Resource +unsafe impl Sync for Resource where T: Send, { } -/// A hardware peripheral as a resource -/// -/// To assign a ceiling to a peripheral, use the -/// [`peripherals!`](./macro.peripherals.html) macro -pub struct Peripheral -where - P: 'static, -{ - peripheral: cortex_m::peripheral::Peripheral

, - _ceiling: PhantomData, -} +pub struct Threshold(u8); -impl Peripheral -where - PC: GreaterThanOrEqual, - PC: LessThanOrEqual, -{ - #[doc(hidden)] - pub const unsafe fn _new(peripheral: cortex_m::peripheral::Peripheral

,) - -> Self { - Peripheral { - _ceiling: PhantomData, - peripheral: peripheral, - } +impl Threshold { + pub unsafe fn new(value: u8) -> Self { + Threshold(value) } } -impl Peripheral { - /// See [Resource.access](./struct.Resource.html#method.access) - pub fn access<'cs, TP, PT>( - &'static self, - _task_priority: &Priority, - _preemption_threshold: &'cs Threshold, - ) -> Ref<'cs, Periph> - where - PC: GreaterThanOrEqual, - PT: GreaterThanOrEqual, - { - unsafe { Ref::new(&*self.peripheral.get()) } - } -} +impl !Send for Threshold {} -unsafe impl Sync for Peripheral {} - -/// Runs the closure `f` "atomically" -/// -/// No task can preempt the execution of the closure -pub fn atomic(f: F) -> R -where - F: FnOnce(&TMax) -> R, -{ - let primask = ::cortex_m::register::primask::read(); - ::cortex_m::interrupt::disable(); - - let r = f(&Threshold { _marker: PhantomData }); - - // If the interrupts were active before our `disable` call, then re-enable - // them. Otherwise, keep them disabled - if primask.is_active() { - unsafe { ::cortex_m::interrupt::enable() } - } - - r -} - -/// Disables a `task` -/// -/// The task won't run even if the underlying interrupt is raised -pub fn disable(_task: fn(T, Priority, Threshold)) -where - T: Context + Nr, -{ - // NOTE(safe) zero sized type - let _task = unsafe { ptr::read(0x0 as *const T) }; - - // NOTE(safe) atomic write - unsafe { (*_NVIC.get()).disable(_task) } -} - -/// Enables a `task` -pub fn enable(_task: fn(T, Priority, Threshold)) -where - T: Context + Nr, -{ - // NOTE(safe) zero sized type - let _task = unsafe { ptr::read(0x0 as *const T) }; - - // NOTE(safe) atomic write - unsafe { (*_NVIC.get()).enable(_task) } -} - -/// Converts a shifted hardware priority into a logical priority -pub fn hw2logical(hw: u8) -> u8 { - (1 << PRIORITY_BITS) - (hw >> (8 - PRIORITY_BITS)) -} - -/// Converts a logical priority into a shifted hardware priority, as used by the -/// NVIC and the BASEPRI register -/// -/// # Panics -/// -/// This function panics if `logical` is outside the closed range -/// `[1, 1 << PRIORITY_BITS]`. Where `PRIORITY_BITS` is the number of priority -/// bits used by the device specific NVIC implementation. -pub fn logical2hw(logical: u8) -> u8 { - assert!(logical >= 1 && logical <= (1 << PRIORITY_BITS)); - - ((1 << PRIORITY_BITS) - logical) << (8 - PRIORITY_BITS) -} - -/// Requests the execution of a `task` -pub fn request(_task: fn(T, Priority, Threshold)) -where - T: Context + Nr, -{ - let nvic = unsafe { &*_NVIC.get() }; - - match () { - #[cfg(debug_assertions)] - () => { - // NOTE(safe) zero sized type - let task = unsafe { core::ptr::read(0x0 as *const T) }; - // NOTE(safe) atomic read - assert!(!nvic.is_pending(task), - "Task is already in the pending state"); - } - #[cfg(not(debug_assertions))] - () => {} - } - - // NOTE(safe) zero sized type - let task = unsafe { core::ptr::read(0x0 as *const T) }; - - // NOTE(safe) atomic write - nvic.set_pending(task); -} - -#[doc(hidden)] -pub fn _validate_priority(_: &Priority) -where - TP: Cmp + LessThanOrEqual, -{ -} - -/// Preemption threshold -pub struct Threshold { - _marker: PhantomData, -} - -impl Threshold { - /// Raises the preemption threshold to match the `resource` ceiling - #[cfg(not(thumbv6m))] - pub fn raise(&self, _resource: &'static RES, f: F) -> R - where - RES: ResourceLike, - RC: Cmp + Cmp + Unsigned, - F: FnOnce(&Threshold) -> R, - { - unsafe { - let old_basepri = basepri::read(); - basepri_max::write(logical2hw(RC::to_u8())); - barrier!(); - let ret = f(&Threshold { _marker: PhantomData }); - barrier!(); - basepri::write(old_basepri); - ret - } - } -} - -impl !Send for Threshold {} - -/// Priority -pub struct Priority { - _marker: PhantomData, -} - -impl Priority -where - T: Unsigned, -{ - #[doc(hidden)] - pub fn _hw() -> u8 { - logical2hw(T::to_u8()) - } -} - -impl !Send for Priority {} - -/// Maps a `Resource` / `Peripheral` to its ceiling -/// -/// Do not implement this trait yourself. This is an implementation detail. -pub unsafe trait ResourceLike { - /// The ceiling of the resource - type Ceiling; -} - -unsafe impl ResourceLike for Peripheral { - type Ceiling = PC; -} - -unsafe impl ResourceLike for Resource { - type Ceiling = RC; -} - -/// Type-level `>=` operator -/// -/// Do not implement this trait yourself. This is an implementation detail. -pub unsafe trait GreaterThanOrEqual {} - -/// Type-level `<=` operator -/// -/// Do not implement this trait yourself. This is an implementation detail. -pub unsafe trait LessThanOrEqual {} - -/// A macro to assign ceilings to peripherals -/// -/// **NOTE** A peripheral instance, like RCC, can only be bound to a *single* -/// ceiling. Trying to use this macro to bind the same peripheral to several -/// ceiling will result in a compiler error. -/// -/// # Example -/// -/// NOTE With device crates generated using svd2rust 0.8+ you can omit the -/// register_block field. -/// -/// ``` ignore -/// #[macro_use] -/// extern crate cortex_m_rtfm; -/// // device crate generated using `svd2rust` -/// extern crate stm32f30x; -/// -/// peripherals!(stm32f30x, { -/// GPIOA: Peripheral { -/// register_block: Gpioa, -/// ceiling: C1, -/// }, -/// RCC: Peripheral { -/// register_block: Rcc, -/// ceiling: C0, -/// }, -/// }); -/// ``` #[macro_export] -macro_rules! peripherals { - ($device:ident, { - $($PERIPHERAL:ident: Peripheral { - register_block: $RegisterBlock:ident, - ceiling: $C:ident, - },)+ - }) => { - $( - #[allow(private_no_mangle_statics)] - #[no_mangle] - static $PERIPHERAL: - $crate::Peripheral<::$device::$RegisterBlock, $crate::$C> = - unsafe { $crate::Peripheral::_new(::$device::$PERIPHERAL) }; - )+ +macro_rules! task { + ($NAME:ident, $body:path) => { + #[allow(non_snake_case)] + #[no_mangle] + pub unsafe extern "C" fn $NAME() { + let f: fn($crate::Threshold, ::$NAME::Resources) = $body; + + f( + $crate::Threshold::new(::$NAME::$NAME), + ::$NAME::Resources::new(), + ); + } }; - ($device:ident, { - $($PERIPHERAL:ident: Peripheral { - ceiling: $C:ident, - },)+ + ($NAME:ident, $body:path, $local:ident { + $($var:ident: $ty:ty = $expr:expr;)+ }) => { - $( - #[allow(private_no_mangle_statics)] - #[no_mangle] - static $PERIPHERAL: - $crate::Peripheral<::$device::$PERIPHERAL, $crate::$C> = - unsafe { $crate::Peripheral::_new(::$device::$PERIPHERAL) }; - )+ - } + struct $local { + $($var: $ty,)+ + } + + #[allow(non_snake_case)] + #[no_mangle] + pub unsafe extern "C" fn $NAME() { + let f: fn( + $crate::Threshold, + &mut $local, + ::$NAME::Resources, + ) = $body; + + static mut LOCAL: $local = $local { + $($var: $expr,)+ + }; + + f( + $crate::Threshold::new(::$NAME::$NAME), + &mut LOCAL, + ::$NAME::Resources::new(), + ); + } + }; } -/// A macro to declare tasks -/// -/// **NOTE** This macro will expand to a `main` function. -/// -/// Each `$task` is bound to an `$Interrupt` handler and has a priority `$P`. -/// The minimum priority of a task is `P1`. `$enabled` indicates whether the -/// task will be enabled before `idle` runs. -/// -/// The `$Interrupt` handlers are defined in the `$device` crate. -/// -/// Apart from defining the listed `$tasks`, the `init` and `idle` functions -/// must be defined as well. `init` has signature `fn(P0, &TMax)`, and `idle` -/// has signature `fn(P0) -> !`. -/// -/// # Example -/// -/// ``` ignore -/// #[feature(used)] -/// #[no_std] -/// -/// extern crate cortex_m_rt; -/// #[macro_use] -/// extern crate cortex_m_rtfm as rtfm; -/// // device crate generated using `svd2rust` -/// extern crate stm32f30x; -/// -/// use rtfm::{P0, P1, P2, T0, T1, T2, TMax}; -/// use stm32f30x::interrupt::{Exti0, Tim7}; -/// -/// tasks!(stm32f30x, { -/// periodic: Task { -/// interrupt: Tim7, -/// priority: P1, -/// enabled: true, -/// }, -/// button: Task { -/// interrupt: Exti0, -/// priority: P2, -/// enabled: true, -/// }, -/// }); -/// -/// fn init(priority: P0, threshold: &TMax) { -/// // .. -/// } -/// -/// fn idle(priority: P0, threshold: T0) -> ! { -/// // Sleep -/// loop { -/// rtfm::wfi(); -/// } -/// } -/// -/// // NOTE signature must match the tasks! declaration -/// fn periodic(task: Tim7, priority: P1, threshold: T1) { -/// // .. -/// } -/// -/// fn button(task: Exti0, priority: P2, threshold: T2) { -/// // .. -/// } -/// ``` -#[macro_export] -macro_rules! tasks { - ($device:ident, { - $($task:ident: Task { - interrupt:$Interrupt:ident, - priority: $P:ident, - enabled: $enabled:expr, - },)* - }) => { - fn main() { - $crate::atomic(|t_max| { - fn validate_signature(_: fn($crate::P0, &$crate::TMax)) {} +#[allow(non_camel_case_types)] +#[doc(hidden)] +pub enum Exception { + /// System service call via SWI instruction + SVCALL, + /// Pendable request for system service + PENDSV, + /// System tick timer + SYS_TICK, +} - validate_signature(init); - let p0 = unsafe { ::core::mem::transmute::<_, P0>(()) }; - init(p0, t_max); - set_priorities(); - enable_tasks(); - }); - - fn validate_signature(_: fn($crate::P0, $crate::T0) -> !) {} - - validate_signature(idle); - let p0 = unsafe { ::core::mem::transmute::<_, P0>(()) }; - let t0 = unsafe { ::core::mem::transmute::<_, T0>(()) }; - idle(p0, t0); - - fn set_priorities() { - // NOTE(safe) this function runs in an interrupt free context - let _nvic = unsafe { &*$crate::_NVIC.get() }; - - $( - { - let hw = $crate::$P::_hw(); - unsafe { - _nvic.set_priority( - ::$device::interrupt::Interrupt::$Interrupt, - hw, - ); - } - } - )* - - // TODO freeze the NVIC.IPR register using the MPU, if available - } - - fn enable_tasks() { - // NOTE(safe) this function runs in an interrupt free context - let _nvic = unsafe { &*$crate::_NVIC.get() }; - - $( - if $enabled { - $crate::enable(::$task); - } - )* - } - - #[allow(dead_code)] - #[link_section = ".rodata.interrupts"] - #[used] - static INTERRUPTS: ::$device::interrupt::Handlers = - ::$device::interrupt::Handlers { - $( - $Interrupt: { - extern "C" fn $task( - task: ::$device::interrupt::$Interrupt - ) { - fn validate_signature( - _: fn(::$device::interrupt::$Interrupt, - $crate::Priority, - $crate::Threshold)) {} - validate_signature(::$task); - let p = unsafe { - ::core::mem::transmute::<_, $crate::$P>(()) - }; - let t = unsafe { - ::core::mem::transmute(()) - }; - $crate::_validate_priority(&p); - ::$task(task, p, t) - } - - $task - }, - )* - ..::$device::interrupt::DEFAULT_HANDLERS - }; +impl Exception { + #[doc(hidden)] + pub fn nr(&self) -> usize { + match *self { + Exception::SVCALL => 11, + Exception::PENDSV => 14, + Exception::SYS_TICK => 15, } } } - -include!(concat!(env!("OUT_DIR"), "/prio.rs")); diff --git a/tests/cfail.rs b/tests/cfail.rs deleted file mode 100644 index 44c982ce5a..0000000000 --- a/tests/cfail.rs +++ /dev/null @@ -1,16 +0,0 @@ -extern crate compiletest_rs as compiletest; - -use std::path::PathBuf; - -use compiletest::common::Mode; - -#[test] -fn cfail() { - let mut config = compiletest::default_config(); - config.mode = Mode::CompileFail; - config.src_base = PathBuf::from(format!("tests/cfail")); - config.target_rustcflags = - Some("-L target/debug -L target/debug/deps ".to_string()); - - compiletest::run_tests(&config); -} diff --git a/tests/cfail/access.rs b/tests/cfail/access.rs deleted file mode 100644 index b35fb371e1..0000000000 --- a/tests/cfail/access.rs +++ /dev/null @@ -1,35 +0,0 @@ -extern crate cortex_m_rtfm as rtfm; - -use rtfm::{C1, C2, C3, C4, C5, P2, Resource, T2}; - -static R1: Resource = Resource::new(0); -static R2: Resource = Resource::new(0); -static R3: Resource = Resource::new(0); -static R4: Resource = Resource::new(0); -static R5: Resource = Resource::new(0); -static R6: Resource = Resource::new(0); - -fn j1(prio: P2, thr: T2) { - thr.raise( - &R1, |thr| { - // NOTE PT = Preemption Threshold, TP = Task Priority - - // CAN access a resource with ceiling RC when PT > RC - let r2 = R2.access(&prio, thr); - - // CAN access a resource with ceiling RC when PT == RC - let r3 = R3.access(&prio, thr); - - // CAN'T access a resource with ceiling RC when PT < RC - let r4 = R4.access(&prio, thr); - //~^ error - - // CAN'T access a resource with ceiling RC when RC < TP - let r5 = R5.access(&prio, thr); - //~^ error - - // CAN access a resource with ceiling RC when RC == tP - let r6 = R6.access(&prio, thr); - } - ); -} diff --git a/tests/cfail/ceiling.rs b/tests/cfail/ceiling.rs deleted file mode 100644 index 118dbcf6f8..0000000000 --- a/tests/cfail/ceiling.rs +++ /dev/null @@ -1,29 +0,0 @@ -extern crate cortex_m_rtfm as rtfm; - -use rtfm::{C2, C3, P0, P2, Resource, T2}; - -static R1: Resource<(), C3> = Resource::new(()); - -fn j1(prio: P2, thr: T2) { - let t3 = thr.raise( - &R1, |thr| { - // forbidden: ceiling token can't outlive the critical section - thr //~ error - } - ); - - // Would be bad: lockless access to a resource with ceiling = 3 - let r2 = R1.access(&prio, t3); -} - -fn j2(prio: P0) { - let c16 = rtfm::atomic( - |c16| { - // forbidden: ceiling token can't outlive the critical section - c16 //~ error - }, - ); - - // Would be bad: lockless access to a resource with ceiling = 16 - let r1 = R1.access(&prio, c16); -} diff --git a/tests/cfail/lock.rs b/tests/cfail/lock.rs deleted file mode 100644 index 64753ac1de..0000000000 --- a/tests/cfail/lock.rs +++ /dev/null @@ -1,36 +0,0 @@ -extern crate cortex_m_rtfm as rtfm; - -use rtfm::{CMax, C2, P1, P2, P3, PMax, Resource, T1, T2, T3, TMax}; - -static R1: Resource = Resource::new(0); - -// You don't need to raise the ceiling to access a resource with ceiling equal -// to the task priority. -fn j1(prio: P2, thr: T2) { - thr.raise(&R1, |_| {}); - //~^ error - - // OK - let r1 = R1.access(&prio, &thr); -} - -// You CAN access a resource with ceiling C from a task with priority P if C > P -// if you raise the preemption threshold first -fn j2(prio: P1, thr: T1) { - // OK - thr.raise(&R1, |thr| { let r1 = R1.access(&prio, thr); }) -} - -static R2: Resource = Resource::new(0); - -// Tasks with priority less than P16 can't access a resource with ceiling CMax -fn j4(prio: P1, thr: T1) { - thr.raise(&R2, |thr| {}); - //~^ error -} - -// Only tasks with priority P16 can directly access a resource with ceiling CMax -fn j5(prio: PMax, thr: TMax) { - // OK - let r2 = R2.access(&prio, &thr); -} diff --git a/tests/cfail/peripherals-alias-1.rs b/tests/cfail/peripherals-alias-1.rs deleted file mode 100644 index 124f3510b8..0000000000 --- a/tests/cfail/peripherals-alias-1.rs +++ /dev/null @@ -1,68 +0,0 @@ -// error-pattern: has already been defined - -#![feature(used)] - -#[macro_use] -extern crate cortex_m_rtfm as rtfm; - -use rtfm::{P0, P1, T0, TMax}; -use device::interrupt::Exti0; - -peripherals!(device, { - GPIOA: Peripheral { - register_block: Gpioa, - ceiling: C1, - }, - // WRONG: peripheral alias - GPIOA: Peripheral { - register_block: Gpioa, - ceiling: C2, - }, -}); - -tasks!(device, {}); - -fn init(_: P0, _: &TMax) {} - -fn idle(_: P0, _: T0) -> ! { - loop {} -} - -fn j1(_task: Exti0, _prio: P1) {} - -// fake device crate -extern crate core; -extern crate cortex_m; - -mod device { - use cortex_m::peripheral::Peripheral; - - pub const GPIOA: Peripheral = unsafe { Peripheral::new(0x0) }; - - pub struct Gpioa; - - pub mod interrupt { - use cortex_m::interrupt::Nr; - - extern "C" fn default_handler(_: T) {} - - pub struct Handlers { - pub Exti0: extern "C" fn(Exti0), - } - - pub struct Exti0; - - pub enum Interrupt { - Exti0, - } - - unsafe impl Nr for Interrupt { - fn nr(&self) -> u8 { - 0 - } - } - - pub const DEFAULT_HANDLERS: Handlers = - Handlers { Exti0: default_handler }; - } -} diff --git a/tests/cfail/peripherals-alias-2.rs b/tests/cfail/peripherals-alias-2.rs deleted file mode 100644 index b50931ee0a..0000000000 --- a/tests/cfail/peripherals-alias-2.rs +++ /dev/null @@ -1,74 +0,0 @@ -// error-pattern: symbol `GPIOA` is already defined - -#![feature(const_fn)] -#![feature(used)] - -#[macro_use] -extern crate cortex_m_rtfm as rtfm; - -use rtfm::{P0, P1, T0, TMax}; -use device::interrupt::Exti0; - -peripherals!(device, { - GPIOA: Peripheral { - register_block: Gpioa, - ceiling: C1, - }, -}); - -mod foo { - // WRONG: peripheral alias - peripherals!(device, { - GPIOA: Peripheral { - register_block: Gpioa, - ceiling: C2, - }, - }); -} - -tasks!(device, {}); - -fn init(_: P0, _: &TMax) {} - -fn idle(_: P0, _: T0) -> ! { - loop {} -} - -fn j1(_task: Exti0, _prio: P1) {} - -// fake device crate -extern crate core; -extern crate cortex_m; - -mod device { - use cortex_m::peripheral::Peripheral; - - pub const GPIOA: Peripheral = unsafe { Peripheral::new(0x0) }; - - pub struct Gpioa; - - pub mod interrupt { - use cortex_m::interrupt::Nr; - - extern "C" fn default_handler(_: T) {} - - pub struct Handlers { - pub Exti0: extern "C" fn(Exti0), - } - - pub struct Exti0; - - pub enum Interrupt { - Exti0, - } - - unsafe impl Nr for Interrupt { - fn nr(&self) -> u8 { - 0 - } - } - - pub const DEFAULT_HANDLERS: Handlers = - Handlers { Exti0: default_handler }; - } -} diff --git a/tests/cfail/race-1.rs b/tests/cfail/race-1.rs deleted file mode 100644 index 8d32c42e06..0000000000 --- a/tests/cfail/race-1.rs +++ /dev/null @@ -1,42 +0,0 @@ -extern crate cortex_m_rtfm as rtfm; - -use rtfm::{C2, P1, P3, Resource, T1, T3}; - -static R1: Resource = Resource::new(0); - -fn j1(prio: P1, thr: T1) { - thr.raise( - &R1, |thr| { - let r1 = R1.access(&prio, thr); - - // `j2` preempts this critical section - rtfm::request(j2); - } - ); -} - -fn j2(_task: Task, prio: P3, thr: T3) { - rtfm::atomic( - |thr| { - // OK C2 (R1's ceiling) <= T16 (preemption threshold) - // BAD C2 (R1's ceiling) < P3 (j2's priority) - let r1 = R1.access(&prio, &thr); - //~^ error - }, - ); -} - -// glue -extern crate cortex_m; - -use cortex_m::ctxt::Context; -use cortex_m::interrupt::Nr; - -struct Task; - -unsafe impl Context for Task {} -unsafe impl Nr for Task { - fn nr(&self) -> u8 { - 0 - } -} diff --git a/tests/cfail/race-2.rs b/tests/cfail/race-2.rs deleted file mode 100644 index f44856e0ef..0000000000 --- a/tests/cfail/race-2.rs +++ /dev/null @@ -1,43 +0,0 @@ -extern crate cortex_m_rtfm as rtfm; - -use rtfm::{C2, C4, P1, P3, Resource, T1, T3}; - -static R1: Resource = Resource::new(0); -static R2: Resource = Resource::new(0); - -fn j1(prio: P1, thr: T1) { - thr.raise( - &R1, |thr| { - let r1 = R1.access(&prio, thr); - - // `j2` preempts this critical section - rtfm::request(j2); - } - ); -} - -fn j2(_task: Task, prio: P3, thr: T3) { - thr.raise( - &R2, |thr| { - // OK C2 (R1's ceiling) <= T4 (preemption threshold) - // BAD C2 (R1's ceiling) < P3 (j2's priority) - let r1 = R1.access(&prio, thr); - //~^ error - } - ); -} - -// glue -extern crate cortex_m; - -use cortex_m::ctxt::Context; -use cortex_m::interrupt::Nr; - -struct Task; - -unsafe impl Context for Task {} -unsafe impl Nr for Task { - fn nr(&self) -> u8 { - 0 - } -} diff --git a/tests/cfail/raise.rs b/tests/cfail/raise.rs deleted file mode 100644 index 3d7e564721..0000000000 --- a/tests/cfail/raise.rs +++ /dev/null @@ -1,24 +0,0 @@ -extern crate cortex_m_rtfm as rtfm; - -use rtfm::{C2, CMax, P1, P3, Resource, T1, T3}; - -static R1: Resource = Resource::new(0); - -// You CAN'T use `raise` to lower the preemption level -fn j1(prio: P3, thr: T3) { - thr.raise(&R1, |thr| {}); - //~^ error -} - -static R2: Resource = Resource::new(0); - -// You CAN'T `raise` the preemption level to the maximum -fn j2(prio: P1, thr: T1) { - thr.raise(&R2, |thr| {}); - //~^ error - - // Instead use `rtfm::atomic` to access a resource with ceiling C16 - rtfm::atomic(|thr| { - let r2 = R2.access(&prio, thr); - }); -} diff --git a/tests/cfail/tasks-p0.rs b/tests/cfail/tasks-p0.rs deleted file mode 100644 index 588ac5d035..0000000000 --- a/tests/cfail/tasks-p0.rs +++ /dev/null @@ -1,92 +0,0 @@ -// error-pattern: expected struct `typenum::Equal`, found struct `typenum::Greater` - -#![feature(used)] - -#[macro_use] -extern crate cortex_m_rtfm as rtfm; - -use rtfm::{P0, P1, T0, T1, TMax}; -use device::interrupt::Exti0; - -// WRONG: Tasks can't have a priority of 0. -// Only idle and init can have a priority of 0. -tasks!(device, { - j1: Task { - interrupt: Exti0, - priority: P0, - enabled: true, - }, -}); - -fn init(_: P0, _: &TMax) {} - -fn idle(_: P0, _: T0) -> ! { - loop {} -} - -fn j1(_task: Exti0, _prio: P1, _thr: T1) {} - -// fake device crate -extern crate core; -extern crate cortex_m; - -mod device { - pub mod interrupt { - use cortex_m::ctxt::Context; - use cortex_m::interrupt::Nr; - - extern "C" fn default_handler(_: T) {} - - pub struct Handlers { - pub Exti0: extern "C" fn(Exti0), - pub Exti1: extern "C" fn(Exti1), - pub Exti2: extern "C" fn(Exti2), - } - - pub struct Exti0; - pub struct Exti1; - pub struct Exti2; - - pub enum Interrupt { - Exti0, - Exti1, - Exti2, - } - - unsafe impl Nr for Interrupt { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti0 {} - - unsafe impl Nr for Exti0 { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti1 {} - - unsafe impl Nr for Exti1 { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti2 {} - - unsafe impl Nr for Exti2 { - fn nr(&self) -> u8 { - 0 - } - } - - pub const DEFAULT_HANDLERS: Handlers = Handlers { - Exti0: default_handler, - Exti1: default_handler, - Exti2: default_handler, - }; - } -} diff --git a/tests/cfail/tasks-same-handler.rs b/tests/cfail/tasks-same-handler.rs deleted file mode 100644 index fdd8b063cd..0000000000 --- a/tests/cfail/tasks-same-handler.rs +++ /dev/null @@ -1,98 +0,0 @@ -// error-pattern: field `Exti0` specified more than once - -#![feature(used)] - -#[macro_use] -extern crate cortex_m_rtfm as rtfm; - -use rtfm::{P0, P1, P2, T0, T1, T2, TMax}; -use device::interrupt::{Exti0, Exti1}; - -// WRONG: Two tasks mapped to the same interrupt handler -tasks!(device, { - j1: Task { - interrupt: Exti0, - priority: P1, - enabled: true, - }, - j2: Task { - interrupt: Exti0, - priority: P2, - enabled: true, - }, -}); - -fn init(_: P0, _: &TMax) {} - -fn idle(_: P0, _: T0) -> ! { - loop {} -} - -fn j1(_task: Exti0, _prio: P1, _thr: T1) {} - -fn j2(_task: Exti0, _prio: P2, _thr: T2) {} - -// fake device crate -extern crate core; -extern crate cortex_m; - -mod device { - pub mod interrupt { - use cortex_m::ctxt::Context; - use cortex_m::interrupt::Nr; - - extern "C" fn default_handler(_: T) {} - - pub struct Handlers { - pub Exti0: extern "C" fn(Exti0), - pub Exti1: extern "C" fn(Exti1), - pub Exti2: extern "C" fn(Exti2), - } - - pub struct Exti0; - pub struct Exti1; - pub struct Exti2; - - pub enum Interrupt { - Exti0, - Exti1, - Exti2, - } - - unsafe impl Nr for Interrupt { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti0 {} - - unsafe impl Nr for Exti0 { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti1 {} - - unsafe impl Nr for Exti1 { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti2 {} - - unsafe impl Nr for Exti2 { - fn nr(&self) -> u8 { - 0 - } - } - - pub const DEFAULT_HANDLERS: Handlers = Handlers { - Exti0: default_handler, - Exti1: default_handler, - Exti2: default_handler, - }; - } -} diff --git a/tests/cfail/tasks-wrong-idle.rs b/tests/cfail/tasks-wrong-idle.rs deleted file mode 100644 index e6ff77904d..0000000000 --- a/tests/cfail/tasks-wrong-idle.rs +++ /dev/null @@ -1,89 +0,0 @@ -// error-pattern: mismatched types - -#![feature(used)] - -#[macro_use] -extern crate cortex_m_rtfm as rtfm; - -use device::interrupt::Exti0; -use rtfm::{P0, P1, T0, T1, TMax}; - -tasks!(device, { - j1: Task { - interrupt: Exti0, - priority: P1, - enabled: true, - }, -}); - -fn init(_: P0, _: &TMax) {} - -// WRONG. `idle` must have signature `fn(P0, C0) -> !` -fn idle(_: P0, _: T0) {} - -fn j1(_task: Exti0, _prio: P1, _thr: T1) {} - -// fake device crate -extern crate core; -extern crate cortex_m; - -mod device { - pub mod interrupt { - use cortex_m::ctxt::Context; - use cortex_m::interrupt::Nr; - - extern "C" fn default_handler(_: T) {} - - pub struct Handlers { - pub Exti0: extern "C" fn(Exti0), - pub Exti1: extern "C" fn(Exti1), - pub Exti2: extern "C" fn(Exti2), - } - - pub struct Exti0; - pub struct Exti1; - pub struct Exti2; - - pub enum Interrupt { - Exti0, - Exti1, - Exti2, - } - - unsafe impl Nr for Interrupt { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti0 {} - - unsafe impl Nr for Exti0 { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti1 {} - - unsafe impl Nr for Exti1 { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti2 {} - - unsafe impl Nr for Exti2 { - fn nr(&self) -> u8 { - 0 - } - } - - pub const DEFAULT_HANDLERS: Handlers = Handlers { - Exti0: default_handler, - Exti1: default_handler, - Exti2: default_handler, - }; - } -} diff --git a/tests/cfail/tasks-wrong-init.rs b/tests/cfail/tasks-wrong-init.rs deleted file mode 100644 index d12b427d8c..0000000000 --- a/tests/cfail/tasks-wrong-init.rs +++ /dev/null @@ -1,91 +0,0 @@ -// error-pattern: mismatched types - -#![feature(used)] - -#[macro_use] -extern crate cortex_m_rtfm as rtfm; - -use rtfm::{P0, P1, T0, T1, TMax}; -use device::interrupt::Exti0; - -tasks!(device, { - j1: Task { - interrupt: Exti0, - priority: P1, - enabled: true, - }, -}); - -// WRONG. `init` must have signature `fn(P0, &TMax)` -fn init(_: P0, _: &T1) {} - -fn idle(_: P0, _: T0) -> ! { - loop {} -} - -fn j1(_task: Exti0, _prio: P1, _thr: T1) {} - -// fake device crate -extern crate core; -extern crate cortex_m; - -mod device { - pub mod interrupt { - use cortex_m::ctxt::Context; - use cortex_m::interrupt::Nr; - - extern "C" fn default_handler(_: T) {} - - pub struct Handlers { - pub Exti0: extern "C" fn(Exti0), - pub Exti1: extern "C" fn(Exti1), - pub Exti2: extern "C" fn(Exti2), - } - - pub struct Exti0; - pub struct Exti1; - pub struct Exti2; - - pub enum Interrupt { - Exti0, - Exti1, - Exti2, - } - - unsafe impl Nr for Interrupt { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti0 {} - - unsafe impl Nr for Exti0 { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti1 {} - - unsafe impl Nr for Exti1 { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti2 {} - - unsafe impl Nr for Exti2 { - fn nr(&self) -> u8 { - 0 - } - } - - pub const DEFAULT_HANDLERS: Handlers = Handlers { - Exti0: default_handler, - Exti1: default_handler, - Exti2: default_handler, - }; - } -} diff --git a/tests/cfail/tasks-wrong-priority.rs b/tests/cfail/tasks-wrong-priority.rs deleted file mode 100644 index 4d05d6b0d0..0000000000 --- a/tests/cfail/tasks-wrong-priority.rs +++ /dev/null @@ -1,91 +0,0 @@ -// error-pattern: mismatched types - -#![feature(used)] - -#[macro_use] -extern crate cortex_m_rtfm as rtfm; - -use device::interrupt::Exti0; -use rtfm::{P0, P1, P2, T0, T1, T2, TMax}; - -tasks!(device, { - j1: Task { - interrupt: Exti0, - priority: P1, - enabled: true, - }, -}); - -fn init(_: P0, _: &TMax) {} - -fn idle(_: P0, _: T0) -> ! { - loop {} -} - -// Wrong priority token. Declared P1, got P2 -fn j1(_task: Exti0, _prio: P2, _thr: T2) {} - -// fake device crate -extern crate core; -extern crate cortex_m; - -mod device { - pub mod interrupt { - use cortex_m::ctxt::Context; - use cortex_m::interrupt::Nr; - - extern "C" fn default_handler(_: T) {} - - pub struct Handlers { - pub Exti0: extern "C" fn(Exti0), - pub Exti1: extern "C" fn(Exti1), - pub Exti2: extern "C" fn(Exti2), - } - - pub struct Exti0; - pub struct Exti1; - pub struct Exti2; - - pub enum Interrupt { - Exti0, - Exti1, - Exti2, - } - - unsafe impl Nr for Interrupt { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti0 {} - - unsafe impl Nr for Exti0 { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti1 {} - - unsafe impl Nr for Exti1 { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti2 {} - - unsafe impl Nr for Exti2 { - fn nr(&self) -> u8 { - 0 - } - } - - pub const DEFAULT_HANDLERS: Handlers = Handlers { - Exti0: default_handler, - Exti1: default_handler, - Exti2: default_handler, - }; - } -} diff --git a/tests/cfail/tasks-wrong-task.rs b/tests/cfail/tasks-wrong-task.rs deleted file mode 100644 index 026290a7da..0000000000 --- a/tests/cfail/tasks-wrong-task.rs +++ /dev/null @@ -1,91 +0,0 @@ -// error-pattern: mismatched types - -#![feature(used)] - -#[macro_use] -extern crate cortex_m_rtfm as rtfm; - -use device::interrupt::{Exti0, Exti1}; -use rtfm::{P0, P1, T0, T1, TMax}; - -tasks!(device, { - j1: Task { - interrupt: Exti0, - priority: P1, - enabled: true, - }, -}); - -fn init(_: P0, _: &TMax) {} - -fn idle(_: P0, _: T0) -> ! { - loop {} -} - -// Wrong task token. Declared Exti0, got Exti1 -fn j1(_task: Exti1, _prio: P1, _thr: T1) {} - -// fake device crate -extern crate core; -extern crate cortex_m; - -mod device { - pub mod interrupt { - use cortex_m::ctxt::Context; - use cortex_m::interrupt::Nr; - - extern "C" fn default_handler(_: T) {} - - pub struct Handlers { - pub Exti0: extern "C" fn(Exti0), - pub Exti1: extern "C" fn(Exti1), - pub Exti2: extern "C" fn(Exti2), - } - - pub struct Exti0; - pub struct Exti1; - pub struct Exti2; - - pub enum Interrupt { - Exti0, - Exti1, - Exti2, - } - - unsafe impl Nr for Interrupt { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti0 {} - - unsafe impl Nr for Exti0 { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti1 {} - - unsafe impl Nr for Exti1 { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti2 {} - - unsafe impl Nr for Exti2 { - fn nr(&self) -> u8 { - 0 - } - } - - pub const DEFAULT_HANDLERS: Handlers = Handlers { - Exti0: default_handler, - Exti1: default_handler, - Exti2: default_handler, - }; - } -} diff --git a/tests/cfail/tasks-wrong-threshold.rs b/tests/cfail/tasks-wrong-threshold.rs deleted file mode 100644 index 4fca734e06..0000000000 --- a/tests/cfail/tasks-wrong-threshold.rs +++ /dev/null @@ -1,91 +0,0 @@ -// error-pattern: mismatched types - -#![feature(used)] - -#[macro_use] -extern crate cortex_m_rtfm as rtfm; - -use rtfm::{C2, P0, P1, T0, T2, TMax}; -use device::interrupt::Exti0; - -tasks!(device, { - j1: Task { - interrupt: Exti0, - priority: P1, - enabled: true, - }, -}); - -fn init(_: P0, _: &TMax) {} - -fn idle(_: P0, _: T0) -> ! { - loop {} -} - -// Wrong ceiling token. `prio` and `thr` must match in levels -fn j1(_task: Exti0, _prio: P1, _thr: T2) {} - -// fake device crate -extern crate core; -extern crate cortex_m; - -mod device { - pub mod interrupt { - use cortex_m::ctxt::Context; - use cortex_m::interrupt::Nr; - - extern "C" fn default_handler(_: T) {} - - pub struct Handlers { - pub Exti0: extern "C" fn(Exti0), - pub Exti1: extern "C" fn(Exti1), - pub Exti2: extern "C" fn(Exti2), - } - - pub struct Exti0; - pub struct Exti1; - pub struct Exti2; - - pub enum Interrupt { - Exti0, - Exti1, - Exti2, - } - - unsafe impl Nr for Interrupt { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti0 {} - - unsafe impl Nr for Exti0 { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti1 {} - - unsafe impl Nr for Exti1 { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti2 {} - - unsafe impl Nr for Exti2 { - fn nr(&self) -> u8 { - 0 - } - } - - pub const DEFAULT_HANDLERS: Handlers = Handlers { - Exti0: default_handler, - Exti1: default_handler, - Exti2: default_handler, - }; - } -} diff --git a/tests/cfail/token-transfer.rs b/tests/cfail/token-transfer.rs deleted file mode 100644 index 1d81e8f707..0000000000 --- a/tests/cfail/token-transfer.rs +++ /dev/null @@ -1,159 +0,0 @@ -#![feature(const_fn)] -#![feature(optin_builtin_traits)] -#![feature(used)] - -#[macro_use] -extern crate cortex_m_rtfm as rtfm; - -use core::cell::RefCell; - -use rtfm::{C2, Local, P0, P1, P2, Resource, T0, T1, T2, TMax}; -use device::interrupt::{Exti0, Exti1}; - -tasks!(device, { - t1: Task { - interrupt: Exti0, - priority: P1, - enabled: true, - }, - t2: Task { - interrupt: Exti1, - priority: P2, - enabled: true, - }, -}); - -fn init(_: P0, _: &TMax) {} - -fn idle(_: P0, _: T0) -> ! { - rtfm::request(t1); - rtfm::request(t1); - - loop {} -} - -static CHANNEL: Resource>, C2> = { - //~^ error: Send - Resource::new(RefCell::new(None)) -}; - -static LOCAL: Local = Local::new(0); - -fn t1(mut task: Exti0, ref priority: P1, ref threshold: T1) { - // First run - static FIRST: Local = Local::new(true); - - let first = *FIRST.borrow(&task); - - if first { - // toggle - *FIRST.borrow_mut(&mut task) = false; - } - - if first { - threshold.raise( - &CHANNEL, move |threshold| { - let channel = CHANNEL.access(priority, threshold); - - // BAD: give up task token - *channel.borrow_mut() = Some(task); - } - ); - - return; - } - - let _local = LOCAL.borrow_mut(&mut task); - - // .. - - // `t2` will preempt `t1` - rtfm::request(t2); - - // .. - - // `LOCAL` mutably borrowed up to this point -} - -fn t2(_task: Exti1, ref priority: P2, ref threshold: T2) { - let channel = CHANNEL.access(priority, threshold); - let mut channel = channel.borrow_mut(); - - if let Some(mut other_task) = channel.take() { - // BAD: `t2` has access to `t1`'s task token - // so it can now mutably access local while `t1` is also using it - let _local = LOCAL.borrow_mut(&mut other_task); - - } -} - -// fake device crate -extern crate core; -extern crate cortex_m; - -mod device { - pub mod interrupt { - use cortex_m::ctxt::Context; - use cortex_m::interrupt::Nr; - - extern "C" fn default_handler(_: T) {} - - pub struct Handlers { - pub Exti0: extern "C" fn(Exti0), - pub Exti1: extern "C" fn(Exti1), - pub Exti2: extern "C" fn(Exti2), - } - - pub struct Exti0; - pub struct Exti1; - pub struct Exti2; - - pub enum Interrupt { - Exti0, - Exti1, - Exti2, - } - - unsafe impl Nr for Interrupt { - fn nr(&self) -> u8 { - 0 - } - } - - unsafe impl Context for Exti0 {} - - unsafe impl Nr for Exti0 { - fn nr(&self) -> u8 { - 0 - } - } - - impl !Send for Exti0 {} - - unsafe impl Context for Exti1 {} - - unsafe impl Nr for Exti1 { - fn nr(&self) -> u8 { - 0 - } - } - - impl !Send for Exti1 {} - - unsafe impl Context for Exti2 {} - - unsafe impl Nr for Exti2 { - fn nr(&self) -> u8 { - 0 - } - } - - impl !Send for Exti2 {} - - pub const DEFAULT_HANDLERS: Handlers = Handlers { - Exti0: default_handler, - Exti1: default_handler, - Exti2: default_handler, - }; - } -}