Skip to content

Commit 777d734

Browse files
bors[bot]japaricadamgreig
committed
Merge #122
122: [RFC] `#[interrupt]` r=therealprof a=japaric this PR adds an `interrupt` attribute that's similar to the existing `exception` except that it's used to override device-specific interrupt handlers. The other big difference is that `cortex_m_rt::interrupt` can't be directly used; it must first be imported from a device crate, which re-exports `cortex_m_rt::interrupt`. This is required to check at compile time that the interrupt is valid for the target device. Safe `static mut` variables can be used with `#[interrupt]` handlers. The syntax is the same as `#[exception]`s. More background information can be found in #109. The companion svd2rust PR is rust-embedded/svd2rust#235 r? @rust-embedded/cortex-m Co-authored-by: Jorge Aparicio <[email protected]> Co-authored-by: Adam Greig <[email protected]>
2 parents 0423076 + 7e583e2 commit 777d734

File tree

10 files changed

+310
-6
lines changed

10 files changed

+310
-6
lines changed

cortex-m-rt/ci/script.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ main() {
88
if [ $TARGET = x86_64-unknown-linux-gnu ] && [ $TRAVIS_RUST_VERSION = nightly ]; then
99
( cd macros && cargo check && cargo test )
1010

11-
cargo test --test compiletest
11+
cargo test --features device --test compiletest
1212
fi
1313

1414
local examples=(

cortex-m-rt/macros/src/lib.rs

Lines changed: 144 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -412,10 +412,10 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
412412
let expr = var.expr;
413413

414414
quote!(
415-
static mut #ident_: #ty = #expr;
416-
#[allow(non_snake_case)]
417-
let #ident: &mut #ty = unsafe { &mut #ident_ };
418-
)
415+
static mut #ident_: #ty = #expr;
416+
#[allow(non_snake_case)]
417+
let #ident: &mut #ty = unsafe { &mut #ident_ };
418+
)
419419
}).collect::<Vec<_>>();
420420

421421
quote!(
@@ -436,6 +436,146 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
436436
}
437437
}
438438

439+
/// Attribute to declare an interrupt (AKA device-specific exception) handler
440+
///
441+
/// **IMPORTANT**: This attribute must be used on reachable items (i.e. there must be no private
442+
/// modules between the item and the root of the crate). If the item is in the root of the crate
443+
/// you'll be fine.
444+
///
445+
/// **NOTE**: This attribute is exposed by `cortex-m-rt` only when the `device` feature is enabled.
446+
/// However, that export is not meant to be used directly -- using it will result in a compilation
447+
/// error. You should instead use the device crate (usually generated using `svd2rust`) re-export of
448+
/// that attribute. You need to use the re-export to have the compiler check that the interrupt
449+
/// exists on the target device.
450+
///
451+
/// # Syntax
452+
///
453+
/// ``` ignore
454+
/// extern crate device;
455+
///
456+
/// // the attribute comes from the device crate not from cortex-m-rt
457+
/// use device::interrupt;
458+
///
459+
/// #[interrupt]
460+
/// fn USART1() {
461+
/// // ..
462+
/// }
463+
/// ```
464+
///
465+
/// where the name of the function must be one of the device interrupts.
466+
///
467+
/// # Usage
468+
///
469+
/// `#[interrupt] fn Name(..` overrides the default handler for the interrupt with the given `Name`.
470+
/// These handlers must have signature `[unsafe] fn() [-> !]`. It's possible to add state to these
471+
/// handlers by declaring `static mut` variables at the beginning of the body of the function. These
472+
/// variables will be safe to access from the function body.
473+
///
474+
/// If the interrupt handler has not been overridden it will be dispatched by the default exception
475+
/// handler (`DefaultHandler`).
476+
///
477+
/// # Properties
478+
///
479+
/// Interrupts handlers can only be called by the hardware. Other parts of the program can't refer
480+
/// to the interrupt handlers, much less invoke them as if they were functions.
481+
///
482+
/// `static mut` variables declared within an interrupt handler are safe to access and can be used
483+
/// to preserve state across invocations of the handler. The compiler can't prove this is safe so
484+
/// the attribute will help by making a transformation to the source code: for this reason a
485+
/// variable like `static mut FOO: u32` will become `let FOO: &mut u32;`.
486+
///
487+
/// # Examples
488+
///
489+
/// - Using state within an interrupt handler
490+
///
491+
/// ``` ignore
492+
/// extern crate device;
493+
///
494+
/// use device::interrupt;
495+
///
496+
/// #[interrupt]
497+
/// fn TIM2() {
498+
/// static mut COUNT: i32 = 0;
499+
///
500+
/// // `COUNT` is safe to access and has type `&mut i32`
501+
/// *COUNT += 1;
502+
///
503+
/// println!("{}", COUNT);
504+
/// }
505+
/// ```
506+
#[proc_macro_attribute]
507+
pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream {
508+
let f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function");
509+
510+
assert!(
511+
args.to_string() == "",
512+
"`interrupt` attribute must have no arguments"
513+
);
514+
515+
let ident = f.ident;
516+
let ident_s = ident.to_string();
517+
518+
// XXX should we blacklist other attributes?
519+
let attrs = f.attrs;
520+
let block = f.block;
521+
let stmts = block.stmts;
522+
let unsafety = f.unsafety;
523+
524+
assert!(
525+
f.constness.is_none()
526+
&& f.vis == Visibility::Inherited
527+
&& f.abi.is_none()
528+
&& f.decl.inputs.is_empty()
529+
&& f.decl.generics.params.is_empty()
530+
&& f.decl.generics.where_clause.is_none()
531+
&& f.decl.variadic.is_none()
532+
&& match f.decl.output {
533+
ReturnType::Default => true,
534+
ReturnType::Type(_, ref ty) => match **ty {
535+
Type::Tuple(ref tuple) => tuple.elems.is_empty(),
536+
Type::Never(..) => true,
537+
_ => false,
538+
},
539+
},
540+
"`#[interrupt]` functions must have signature `[unsafe] fn() [-> !]`"
541+
);
542+
543+
let (statics, stmts) = extract_static_muts(stmts);
544+
545+
let vars = statics
546+
.into_iter()
547+
.map(|var| {
548+
let ident = var.ident;
549+
// `let` can't shadow a `static mut` so we must give the `static` a different
550+
// name. We'll create a new name by appending an underscore to the original name
551+
// of the `static`.
552+
let mut ident_ = ident.to_string();
553+
ident_.push('_');
554+
let ident_ = Ident::new(&ident_, Span::call_site());
555+
let ty = var.ty;
556+
let expr = var.expr;
557+
558+
quote!(
559+
static mut #ident_: #ty = #expr;
560+
#[allow(non_snake_case)]
561+
let #ident: &mut #ty = unsafe { &mut #ident_ };
562+
)
563+
}).collect::<Vec<_>>();
564+
565+
let hash = random_ident();
566+
quote!(
567+
#[export_name = #ident_s]
568+
#(#attrs)*
569+
pub #unsafety extern "C" fn #hash() {
570+
interrupt::#ident;
571+
572+
#(#vars)*
573+
574+
#(#stmts)*
575+
}
576+
).into()
577+
}
578+
439579
/// Attribute to mark which function will be called at the beginning of the reset handler.
440580
///
441581
/// **IMPORTANT**: This attribute must be used once in the dependency graph and must be used on a

cortex-m-rt/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,8 @@ extern crate r0;
394394
use core::fmt;
395395
use core::sync::atomic::{self, Ordering};
396396

397+
#[cfg(feature = "device")]
398+
pub use macros::interrupt;
397399
pub use macros::{entry, exception, pre_init};
398400

399401
#[export_name = "error: cortex-m-rt appears more than once in the dependency graph"]
@@ -674,7 +676,7 @@ pub static __EXCEPTIONS: [Vector; 14] = [
674676

675677
// If we are not targeting a specific device we bind all the potential device specific interrupts
676678
// to the default handler
677-
#[cfg(all(not(feature = "device"), not(armv6m)))]
679+
#[cfg(all(any(not(feature = "device"), test), not(armv6m)))]
678680
#[doc(hidden)]
679681
#[link_section = ".vector_table.interrupts"]
680682
#[no_mangle]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#![no_main]
2+
#![no_std]
3+
4+
extern crate cortex_m_rt;
5+
extern crate panic_halt;
6+
7+
use cortex_m_rt::{entry, interrupt};
8+
9+
#[entry]
10+
fn foo() -> ! {
11+
loop {}
12+
}
13+
14+
enum interrupt {
15+
USART1,
16+
}
17+
18+
#[interrupt(true)] //~ ERROR custom attribute panicked
19+
//~^ HELP `interrupt` attribute must have no arguments
20+
fn USART1() {}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#![no_main]
2+
#![no_std]
3+
4+
extern crate cortex_m_rt;
5+
extern crate panic_halt;
6+
7+
use cortex_m_rt::{entry, interrupt};
8+
9+
#[entry]
10+
fn foo() -> ! {
11+
loop {}
12+
}
13+
14+
enum interrupt {
15+
USART1,
16+
}
17+
18+
#[interrupt] //~ ERROR custom attribute panicked
19+
//~^ HELP `#[interrupt]` functions must have signature `[unsafe] fn() [-> !]`
20+
fn USART1(undef: i32) {}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#![no_main]
2+
#![no_std]
3+
4+
extern crate cortex_m_rt;
5+
extern crate panic_halt;
6+
7+
use cortex_m_rt::{entry, interrupt};
8+
9+
#[entry]
10+
fn foo() -> ! {
11+
loop {}
12+
}
13+
14+
enum interrupt {
15+
USART1,
16+
}
17+
18+
#[interrupt] //~ ERROR custom attribute panicked
19+
//~^ HELP `#[interrupt]` functions must have signature `[unsafe] fn() [-> !]`
20+
fn USART1() -> i32 {
21+
0
22+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![no_main]
2+
#![no_std]
3+
4+
extern crate cortex_m_rt;
5+
extern crate panic_halt;
6+
7+
use cortex_m_rt::{entry, interrupt};
8+
9+
#[entry]
10+
fn foo() -> ! {
11+
loop {}
12+
}
13+
14+
enum interrupt {
15+
USART1,
16+
}
17+
18+
// NOTE this looks a bit better when using a device crate:
19+
// "no variant named `foo` found for type `stm32f30x::Interrupt` in the current scope"
20+
#[interrupt] //~ ERROR no variant named `foo` found for type `interrupt` in the current scope
21+
fn foo() {}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#![no_main]
2+
#![no_std]
3+
4+
extern crate cortex_m_rt;
5+
extern crate panic_halt;
6+
7+
use cortex_m_rt::{entry, interrupt};
8+
9+
#[entry]
10+
fn foo() -> ! {
11+
loop {}
12+
}
13+
14+
#[interrupt] //~ ERROR failed to resolve. Use of undeclared type or module `interrupt`
15+
fn USART1() {}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#![no_main]
2+
#![no_std]
3+
4+
extern crate cortex_m_rt;
5+
extern crate panic_halt;
6+
7+
use cortex_m_rt::{entry, interrupt};
8+
9+
#[entry]
10+
fn foo() -> ! {
11+
loop {}
12+
}
13+
14+
enum interrupt {
15+
USART1,
16+
USART2,
17+
}
18+
19+
#[interrupt]
20+
fn USART1() {
21+
static mut COUNT: u64 = 0;
22+
23+
if *COUNT % 2 == 0 {
24+
*COUNT += 1;
25+
} else {
26+
*COUNT *= 2;
27+
}
28+
}
29+
30+
#[interrupt]
31+
fn USART2() {
32+
USART1(); //~ ERROR cannot find function `USART1` in this scope
33+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#![allow(non_camel_case_types)]
2+
#![no_main]
3+
#![no_std]
4+
5+
extern crate cortex_m_rt;
6+
extern crate panic_halt;
7+
8+
use cortex_m_rt::{entry, interrupt};
9+
10+
#[entry]
11+
fn foo() -> ! {
12+
loop {}
13+
}
14+
15+
enum interrupt {
16+
USART1,
17+
}
18+
19+
#[interrupt]
20+
fn USART1() {}
21+
22+
pub mod reachable {
23+
use cortex_m_rt::interrupt;
24+
25+
enum interrupt {
26+
USART1,
27+
}
28+
29+
#[interrupt] //~ ERROR symbol `USART1` is already defined
30+
fn USART1() {}
31+
}

0 commit comments

Comments
 (0)