diff options
author | 2018-09-17 20:12:25 +0200 | |
---|---|---|
committer | 2018-09-18 01:08:38 +0200 | |
commit | fb7368e658ed175a35cdf4a33a02b356aa139523 (patch) | |
tree | 782a280a996604ad2b73f247311110a6df225c77 /cortex-m-rt/macros/src/lib.rs | |
parent | 98ce8a1f5d30e3c5c421c0e9c19c078cea7c0473 (diff) | |
download | cortex-m-fb7368e658ed175a35cdf4a33a02b356aa139523.tar.gz cortex-m-fb7368e658ed175a35cdf4a33a02b356aa139523.tar.zst cortex-m-fb7368e658ed175a35cdf4a33a02b356aa139523.zip |
implement `#[interrupt]`
Diffstat (limited to 'cortex-m-rt/macros/src/lib.rs')
-rw-r--r-- | cortex-m-rt/macros/src/lib.rs | 147 |
1 files changed, 143 insertions, 4 deletions
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 |