aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jorge Aparicio <jorge@japaric.io> 2018-09-17 20:12:25 +0200
committerGravatar Jorge Aparicio <jorge@japaric.io> 2018-09-18 01:08:38 +0200
commitfb7368e658ed175a35cdf4a33a02b356aa139523 (patch)
tree782a280a996604ad2b73f247311110a6df225c77
parent98ce8a1f5d30e3c5c421c0e9c19c078cea7c0473 (diff)
downloadcortex-m-fb7368e658ed175a35cdf4a33a02b356aa139523.tar.gz
cortex-m-fb7368e658ed175a35cdf4a33a02b356aa139523.tar.zst
cortex-m-fb7368e658ed175a35cdf4a33a02b356aa139523.zip
implement `#[interrupt]`
-rw-r--r--cortex-m-rt/Cargo.toml1
-rw-r--r--cortex-m-rt/ci/script.sh2
-rw-r--r--cortex-m-rt/macros/src/lib.rs147
-rw-r--r--cortex-m-rt/src/lib.rs4
-rw-r--r--cortex-m-rt/tests/compile-fail/interrupt-args.rs20
-rw-r--r--cortex-m-rt/tests/compile-fail/interrupt-bad-signature-1.rs20
-rw-r--r--cortex-m-rt/tests/compile-fail/interrupt-bad-signature-2.rs22
-rw-r--r--cortex-m-rt/tests/compile-fail/interrupt-invalid.rs21
-rw-r--r--cortex-m-rt/tests/compile-fail/interrupt-not-reexported.rs15
-rw-r--r--cortex-m-rt/tests/compile-fail/interrupt-soundness.rs33
-rw-r--r--cortex-m-rt/tests/compile-fail/interrupt-twice.rs31
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() {}
+}