diff options
author | 2018-09-17 20:12:25 +0200 | |
---|---|---|
committer | 2018-09-18 01:08:38 +0200 | |
commit | fb7368e658ed175a35cdf4a33a02b356aa139523 (patch) | |
tree | 782a280a996604ad2b73f247311110a6df225c77 | |
parent | 98ce8a1f5d30e3c5c421c0e9c19c078cea7c0473 (diff) | |
download | cortex-m-fb7368e658ed175a35cdf4a33a02b356aa139523.tar.gz cortex-m-fb7368e658ed175a35cdf4a33a02b356aa139523.tar.zst cortex-m-fb7368e658ed175a35cdf4a33a02b356aa139523.zip |
implement `#[interrupt]`
-rw-r--r-- | cortex-m-rt/Cargo.toml | 1 | ||||
-rw-r--r-- | cortex-m-rt/ci/script.sh | 2 | ||||
-rw-r--r-- | cortex-m-rt/macros/src/lib.rs | 147 | ||||
-rw-r--r-- | cortex-m-rt/src/lib.rs | 4 | ||||
-rw-r--r-- | cortex-m-rt/tests/compile-fail/interrupt-args.rs | 20 | ||||
-rw-r--r-- | cortex-m-rt/tests/compile-fail/interrupt-bad-signature-1.rs | 20 | ||||
-rw-r--r-- | cortex-m-rt/tests/compile-fail/interrupt-bad-signature-2.rs | 22 | ||||
-rw-r--r-- | cortex-m-rt/tests/compile-fail/interrupt-invalid.rs | 21 | ||||
-rw-r--r-- | cortex-m-rt/tests/compile-fail/interrupt-not-reexported.rs | 15 | ||||
-rw-r--r-- | cortex-m-rt/tests/compile-fail/interrupt-soundness.rs | 33 | ||||
-rw-r--r-- | cortex-m-rt/tests/compile-fail/interrupt-twice.rs | 31 |
11 files changed, 310 insertions, 6 deletions
diff --git a/cortex-m-rt/Cargo.toml b/cortex-m-rt/Cargo.toml index 41a958d..0123750 100644 --- a/cortex-m-rt/Cargo.toml +++ b/cortex-m-rt/Cargo.toml @@ -18,6 +18,7 @@ cortex-m-rt-macros = { path = "macros", version = "0.1.1" } cortex-m = "0.5.4" panic-abort = "0.3.0" panic-semihosting = "0.4.0" +panic-halt = "0.2.0" [dev-dependencies.rand] default-features = false diff --git a/cortex-m-rt/ci/script.sh b/cortex-m-rt/ci/script.sh index 9933372..3cffcad 100644 --- a/cortex-m-rt/ci/script.sh +++ b/cortex-m-rt/ci/script.sh @@ -8,7 +8,7 @@ main() { if [ $TARGET = x86_64-unknown-linux-gnu ]; then ( cd macros && cargo check && cargo test ) - cargo test --test compiletest + cargo test --features device --test compiletest fi local examples=( diff --git a/cortex-m-rt/macros/src/lib.rs b/cortex-m-rt/macros/src/lib.rs index 804dd64..711cf0b 100644 --- a/cortex-m-rt/macros/src/lib.rs +++ b/cortex-m-rt/macros/src/lib.rs @@ -411,10 +411,10 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { let expr = var.expr; quote!( - static mut #ident_: #ty = #expr; - #[allow(non_snake_case)] - let #ident: &mut #ty = unsafe { &mut #ident_ }; - ) + static mut #ident_: #ty = #expr; + #[allow(non_snake_case)] + let #ident: &mut #ty = unsafe { &mut #ident_ }; + ) }).collect::<Vec<_>>(); quote!( @@ -435,6 +435,145 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { } } +/// Attribute to declare an interrupt (AKA device-specific exception) handler +/// +/// **IMPORTANT**: This attribute must be used on reachable items (i.e. there must be no private +/// modules between the item and the root of the crate). If the item is in the root of the crate +/// you'll be fine. +/// +/// **NOTE**: This attribute is exposed by `cortex-m-rt` only when the `device` feature is enabled. +/// However, that export is not meant to be used directly -- using it will result in a compilation +/// error. You should instead use the device crate (usually generated using `svd2rust`) re-export of +/// that attribute. You need to use the re-export to have the compiler check that the interrupt +/// exists on the target device. +/// +/// # Syntax +/// +/// ``` ignore +/// extern crate device; +/// +/// // the attribute comes from the device crate not from cortex-m-rt +/// use device::interrupt; +/// +/// #[interrupt] +/// fn USART1() { +/// // .. +/// } +/// ``` +/// +/// where the name of the function must be one of the device interrupts. +/// +/// # Usage +/// +/// `#[interrupt] fn Name(..` overrides the default handler for the interrupt with the given `Name`. +/// These handlers must have signature `[unsafe] fn() [-> !]`. It's possible to add state to these +/// handlers by declaring `static mut` variables at the beginning of the body of the function. These +/// variables will be safe to access from the function body. +/// +/// If the interrupt handler has not been overridden it will be dispatched by the default exception +/// handler (`DefaultHandler`). +/// +/// # Properties +/// +/// Interrupts handlers can only be called by the hardware. Other parts of the program can't refer +/// to the interrupt handlers, much less invoke them as if they were functions. +/// +/// `static mut` variables declared within an interrupt handler are safe to access and can be used +/// to preserve state across invocations of the handler. The compiler can't prove this is safe so +/// the attribute will help by making a transformation to the source code: for this reason a +/// variable like `static mut FOO: u32` will become `let FOO: &mut u32;`. +/// +/// # Examples +/// +/// - Using state within an interrupt handler +/// +/// ``` ignore +/// extern crate device; +/// +/// use device::interrupt; +/// +/// #[interrupt] +/// fn TIM2() { +/// static mut COUNT: i32 = 0; +/// +/// // `COUNT` is safe to access and has type `&mut i32` +/// *COUNT += 1; +/// +/// println!("{}", COUNT); +/// } +/// ``` +#[proc_macro_attribute] +pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream { + let f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function"); + + assert!( + args.to_string() == "", + "`interrupt` attribute must have no arguments" + ); + + let ident = f.ident; + let ident_s = ident.to_string(); + + // XXX should we blacklist other attributes? + let attrs = f.attrs; + let block = f.block; + let stmts = block.stmts; + + assert!( + f.constness.is_none() + && f.vis == Visibility::Inherited + && f.abi.is_none() + && f.decl.inputs.is_empty() + && f.decl.generics.params.is_empty() + && f.decl.generics.where_clause.is_none() + && f.decl.variadic.is_none() + && match f.decl.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => match **ty { + Type::Tuple(ref tuple) => tuple.elems.is_empty(), + Type::Never(..) => true, + _ => false, + }, + }, + "`#[interrupt]` functions must have signature `[unsafe] fn() [-> !]`" + ); + + let (statics, stmts) = extract_static_muts(stmts); + + let vars = statics + .into_iter() + .map(|var| { + let ident = var.ident; + // `let` can't shadow a `static mut` so we must give the `static` a different + // name. We'll create a new name by appending an underscore to the original name + // of the `static`. + let mut ident_ = ident.to_string(); + ident_.push('_'); + let ident_ = Ident::new(&ident_, Span::call_site()); + let ty = var.ty; + let expr = var.expr; + + quote!( + static mut #ident_: #ty = #expr; + #[allow(non_snake_case)] + let #ident: &mut #ty = unsafe { &mut #ident_ }; + ) + }).collect::<Vec<_>>(); + + let hash = random_ident(); + quote!( + #[export_name = #ident_s] + #(#attrs)* + pub extern "C" fn #hash() { + interrupt::#ident; + + #(#vars)* + + #(#stmts)* + } + ).into() +} + /// Attribute to mark which function will be called at the beginning of the reset handler. /// /// **IMPORTANT**: This attribute must be used once in the dependency graph and must be used on a diff --git a/cortex-m-rt/src/lib.rs b/cortex-m-rt/src/lib.rs index f3e58d0..7488cc9 100644 --- a/cortex-m-rt/src/lib.rs +++ b/cortex-m-rt/src/lib.rs @@ -394,6 +394,8 @@ extern crate r0; use core::fmt; use core::sync::atomic::{self, Ordering}; +#[cfg(feature = "device")] +pub use macros::interrupt; pub use macros::{entry, exception, pre_init}; #[export_name = "error: cortex-m-rt appears more than once in the dependency graph"] @@ -674,7 +676,7 @@ pub static __EXCEPTIONS: [Vector; 14] = [ // If we are not targeting a specific device we bind all the potential device specific interrupts // to the default handler -#[cfg(all(not(feature = "device"), not(armv6m)))] +#[cfg(all(any(not(feature = "device"), test), not(armv6m)))] #[doc(hidden)] #[link_section = ".vector_table.interrupts"] #[no_mangle] diff --git a/cortex-m-rt/tests/compile-fail/interrupt-args.rs b/cortex-m-rt/tests/compile-fail/interrupt-args.rs new file mode 100644 index 0000000..36af388 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/interrupt-args.rs @@ -0,0 +1,20 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_halt; + +use cortex_m_rt::{entry, interrupt}; + +#[entry] +fn foo() -> ! { + loop {} +} + +enum interrupt { + USART1, +} + +#[interrupt(true)] //~ ERROR custom attribute panicked +//~^ HELP `interrupt` attribute must have no arguments +fn USART1() {} diff --git a/cortex-m-rt/tests/compile-fail/interrupt-bad-signature-1.rs b/cortex-m-rt/tests/compile-fail/interrupt-bad-signature-1.rs new file mode 100644 index 0000000..0222413 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/interrupt-bad-signature-1.rs @@ -0,0 +1,20 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_halt; + +use cortex_m_rt::{entry, interrupt}; + +#[entry] +fn foo() -> ! { + loop {} +} + +enum interrupt { + USART1, +} + +#[interrupt] //~ ERROR custom attribute panicked +//~^ HELP `#[interrupt]` functions must have signature `[unsafe] fn() [-> !]` +fn USART1(undef: i32) {} diff --git a/cortex-m-rt/tests/compile-fail/interrupt-bad-signature-2.rs b/cortex-m-rt/tests/compile-fail/interrupt-bad-signature-2.rs new file mode 100644 index 0000000..0c9000b --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/interrupt-bad-signature-2.rs @@ -0,0 +1,22 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_halt; + +use cortex_m_rt::{entry, interrupt}; + +#[entry] +fn foo() -> ! { + loop {} +} + +enum interrupt { + USART1, +} + +#[interrupt] //~ ERROR custom attribute panicked +//~^ HELP `#[interrupt]` functions must have signature `[unsafe] fn() [-> !]` +fn USART1() -> i32 { + 0 +} diff --git a/cortex-m-rt/tests/compile-fail/interrupt-invalid.rs b/cortex-m-rt/tests/compile-fail/interrupt-invalid.rs new file mode 100644 index 0000000..4e79e7d --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/interrupt-invalid.rs @@ -0,0 +1,21 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_halt; + +use cortex_m_rt::{entry, interrupt}; + +#[entry] +fn foo() -> ! { + loop {} +} + +enum interrupt { + USART1, +} + +// NOTE this looks a bit better when using a device crate: +// "no variant named `foo` found for type `stm32f30x::Interrupt` in the current scope" +#[interrupt] //~ ERROR no variant named `foo` found for type `interrupt` in the current scope +fn foo() {} diff --git a/cortex-m-rt/tests/compile-fail/interrupt-not-reexported.rs b/cortex-m-rt/tests/compile-fail/interrupt-not-reexported.rs new file mode 100644 index 0000000..9530cf4 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/interrupt-not-reexported.rs @@ -0,0 +1,15 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_halt; + +use cortex_m_rt::{entry, interrupt}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[interrupt] //~ ERROR failed to resolve. Use of undeclared type or module `interrupt` +fn USART1() {} diff --git a/cortex-m-rt/tests/compile-fail/interrupt-soundness.rs b/cortex-m-rt/tests/compile-fail/interrupt-soundness.rs new file mode 100644 index 0000000..b473a94 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/interrupt-soundness.rs @@ -0,0 +1,33 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_halt; + +use cortex_m_rt::{entry, interrupt}; + +#[entry] +fn foo() -> ! { + loop {} +} + +enum interrupt { + USART1, + USART2, +} + +#[interrupt] +fn USART1() { + static mut COUNT: u64 = 0; + + if *COUNT % 2 == 0 { + *COUNT += 1; + } else { + *COUNT *= 2; + } +} + +#[interrupt] +fn USART2() { + USART1(); //~ ERROR cannot find function `USART1` in this scope +} diff --git a/cortex-m-rt/tests/compile-fail/interrupt-twice.rs b/cortex-m-rt/tests/compile-fail/interrupt-twice.rs new file mode 100644 index 0000000..b59aea9 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/interrupt-twice.rs @@ -0,0 +1,31 @@ +#![allow(non_camel_case_types)] +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_halt; + +use cortex_m_rt::{entry, interrupt}; + +#[entry] +fn foo() -> ! { + loop {} +} + +enum interrupt { + USART1, +} + +#[interrupt] +fn USART1() {} + +pub mod reachable { + use cortex_m_rt::interrupt; + + enum interrupt { + USART1, + } + + #[interrupt] //~ ERROR symbol `USART1` is already defined + fn USART1() {} +} |