//! Procedural macros for placing statics and functions into RAM, and for //! marking interrupt handlers. #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")] use darling::FromMeta; use proc_macro::{self, Span, TokenStream}; use proc_macro_error::{abort, proc_macro_error}; use quote::quote; #[cfg(feature = "interrupt")] use syn::{ parse, spanned::Spanned, AttrStyle, Attribute, Ident, ItemFn, Meta::Path, ReturnType, Type, Visibility, }; use syn::{ parse::{Parse, ParseStream}, parse_macro_input, AttributeArgs, }; #[derive(Debug, Default, FromMeta)] #[darling(default)] struct RamArgs { rtc_fast: bool, rtc_slow: bool, uninitialized: bool, zeroed: bool, } /// This attribute allows placing statics and functions into ram. /// /// Options that can be specified are rtc_slow or rtc_fast to use the /// RTC slow or RTC fast ram instead of the normal SRAM. /// /// The uninitialized option will skip initialization of the memory /// (e.g. to persist it across resets or deep sleep mode for the RTC RAM) /// /// Not all targets support RTC slow ram. #[proc_macro_attribute] #[proc_macro_error] pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream { let attr_args = parse_macro_input!(args as AttributeArgs); let RamArgs { rtc_fast, rtc_slow, uninitialized, zeroed, } = match FromMeta::from_list(&attr_args) { Ok(v) => v, Err(e) => { return e.write_errors().into(); } }; let item: syn::Item = syn::parse(input).expect("failed to parse input"); #[cfg(not(feature = "rtc_slow"))] if rtc_slow { abort!( Span::call_site(), "rtc_slow is not available for this target" ); } let is_fn = matches!(item, syn::Item::Fn(_)); let section_name = match (is_fn, rtc_fast, rtc_slow, uninitialized, zeroed) { (true, false, false, false, false) => Ok(".rwtext"), (true, true, false, false, false) => Ok(".rtc_fast.text"), (true, false, true, false, false) => Ok(".rtc_slow.text"), (false, false, false, false, false) => Ok(".data"), (false, true, false, false, false) => Ok(".rtc_fast.data"), (false, true, false, true, false) => Ok(".rtc_fast.noinit"), (false, true, false, false, true) => Ok(".rtc_fast.bss"), (false, false, true, false, false) => Ok(".rtc_slow.data"), (false, false, true, true, false) => Ok(".rtc_slow.noinit"), (false, false, true, false, true) => Ok(".rtc_slow.bss"), _ => Err(()), }; let section = match (is_fn, section_name) { (true, Ok(section_name)) => quote! { #[link_section = #section_name] #[inline(never)] // make certain function is not inlined }, (false, Ok(section_name)) => quote! { #[link_section = #section_name] }, (_, Err(_)) => { abort!(Span::call_site(), "Invalid combination of ram arguments"); } }; let output = quote! { #section #item }; output.into() } /// Marks a function as an interrupt handler /// /// Used to handle on of the [interrupts](enum.Interrupt.html). /// /// When specified between braces (`#[interrupt(example)]`) that interrupt will /// be used and the function can have an arbitrary name. Otherwise the name of /// the function must be the name of the interrupt. /// /// Example usage: /// /// ```rust /// #[interrupt] /// fn GPIO() { /// // code /// } /// ``` /// /// The interrupt context can also be supplied by adding a argument to the /// interrupt function for example, on Xtensa based chips: /// /// ```rust /// fn GPIO(context: &mut xtensa_lx_rt::exception::Context) { /// // code /// } /// ``` #[cfg(feature = "interrupt")] #[proc_macro_attribute] pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream { use proc_macro_crate::{crate_name, FoundCrate}; let mut f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function"); let attr_args = parse_macro_input!(args as AttributeArgs); if attr_args.len() > 1 { abort!( Span::call_site(), "This attribute accepts zero or 1 arguments" ) } let ident = f.sig.ident.clone(); let mut ident_s = &ident.clone(); if attr_args.len() == 1 { match &attr_args[0] { syn::NestedMeta::Meta(Path(x)) => { ident_s = x.get_ident().unwrap(); } _ => { abort!( Span::call_site(), format!( "This attribute accepts a string attribute {:?}", attr_args[0] ) ) } } } // XXX should we blacklist other attributes? if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) { return error; } let valid_signature = f.sig.constness.is_none() && f.vis == Visibility::Inherited && f.sig.abi.is_none() && f.sig.generics.params.is_empty() && f.sig.generics.where_clause.is_none() && f.sig.variadic.is_none() && match f.sig.output { ReturnType::Default => true, ReturnType::Type(_, ref ty) => match **ty { Type::Tuple(ref tuple) => tuple.elems.is_empty(), Type::Never(..) => true, _ => false, }, } && f.sig.inputs.len() <= 1; if !valid_signature { return parse::Error::new( f.span(), "`#[interrupt]` handlers must have signature `[unsafe] fn([&mut Context]) [-> !]`", ) .to_compile_error() .into(); } f.sig.ident = Ident::new( &format!("__esp_hal_internal_{}", f.sig.ident), proc_macro2::Span::call_site(), ); #[cfg(feature = "esp32")] let hal_crate = crate_name("esp32-hal"); #[cfg(feature = "esp32s2")] let hal_crate = crate_name("esp32s2-hal"); #[cfg(feature = "esp32s3")] let hal_crate = crate_name("esp32s3-hal"); #[cfg(feature = "esp32c2")] let hal_crate = crate_name("esp32c2-hal"); #[cfg(feature = "esp32c3")] let hal_crate = crate_name("esp32c3-hal"); #[cfg(feature = "esp32c6")] let hal_crate = crate_name("esp32c6-hal"); #[cfg(feature = "esp32h2")] let hal_crate = crate_name("esp32h2-hal"); #[cfg(feature = "esp32")] let hal_crate_name = Ident::new("esp32_hal", Span::call_site().into()); #[cfg(feature = "esp32s2")] let hal_crate_name = Ident::new("esp32s2_hal", Span::call_site().into()); #[cfg(feature = "esp32s3")] let hal_crate_name = Ident::new("esp32s3_hal", Span::call_site().into()); #[cfg(feature = "esp32c2")] let hal_crate_name = Ident::new("esp32c2_hal", Span::call_site().into()); #[cfg(feature = "esp32c3")] let hal_crate_name = Ident::new("esp32c3_hal", Span::call_site().into()); #[cfg(feature = "esp32c6")] let hal_crate_name = Ident::new("esp32c6_hal", Span::call_site().into()); #[cfg(feature = "esp32h2")] let hal_crate_name = Ident::new("esp32h2_hal", Span::call_site().into()); let interrupt_in_hal_crate = match hal_crate { Ok(FoundCrate::Itself) => { quote!( #hal_crate_name::peripherals::Interrupt::#ident_s ) } Ok(FoundCrate::Name(ref name)) => { let ident = Ident::new(&name, Span::call_site().into()); quote!( #ident::peripherals::Interrupt::#ident_s ) } Err(_) => { quote!( crate::peripherals::Interrupt::#ident_s ) } }; f.block.stmts.extend(std::iter::once( syn::parse2(quote! {{ // Check that this interrupt actually exists #interrupt_in_hal_crate; }}) .unwrap(), )); let tramp_ident = Ident::new( &format!("{}_trampoline", f.sig.ident), proc_macro2::Span::call_site(), ); let ident = &f.sig.ident; let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone()); let export_name = ident_s.to_string(); let trap_frame_in_hal_crate = match hal_crate { Ok(FoundCrate::Itself) => { quote!(#hal_crate_name::trapframe::TrapFrame) } Ok(FoundCrate::Name(ref name)) => { let ident = Ident::new(&name, Span::call_site().into()); quote!( #ident::trapframe::TrapFrame ) } Err(_) => { quote!(crate::trapframe::TrapFrame) } }; let context_call = (f.sig.inputs.len() == 1).then(|| Ident::new("context", proc_macro2::Span::call_site())); quote!( macro_rules! foo { () => { }; } foo!(); #(#cfgs)* #(#attrs)* #[doc(hidden)] #[export_name = #export_name] pub unsafe extern "C" fn #tramp_ident(context: &mut #trap_frame_in_hal_crate) { #ident( #context_call ) } #[inline(always)] #f ) .into() } #[cfg(feature = "interrupt")] enum WhiteListCaller { Interrupt, } #[cfg(feature = "interrupt")] fn check_attr_whitelist(attrs: &[Attribute], caller: WhiteListCaller) -> Result<(), TokenStream> { let whitelist = &[ "doc", "link_section", "cfg", "allow", "warn", "deny", "forbid", "cold", "ram", "inline", ]; 'o: for attr in attrs { for val in whitelist { if eq(&attr, &val) { continue 'o; } } let err_str = match caller { WhiteListCaller::Interrupt => { "this attribute is not allowed on an interrupt handler controlled by esp-hal" } }; return Err(parse::Error::new(attr.span(), &err_str) .to_compile_error() .into()); } Ok(()) } /// Returns `true` if `attr.path` matches `name` #[cfg(feature = "interrupt")] fn eq(attr: &Attribute, name: &str) -> bool { attr.style == AttrStyle::Outer && attr.path.is_ident(name) } #[cfg(feature = "interrupt")] fn extract_cfgs(attrs: Vec) -> (Vec, Vec) { let mut cfgs = vec![]; let mut not_cfgs = vec![]; for attr in attrs { if eq(&attr, "cfg") { cfgs.push(attr); } else { not_cfgs.push(attr); } } (cfgs, not_cfgs) } #[derive(Debug)] struct MakeGpioEnumDispatchMacro { name: String, filter: Vec, elements: Vec<(String, usize)>, } impl Parse for MakeGpioEnumDispatchMacro { fn parse(input: ParseStream) -> syn::parse::Result { let name = input.parse::()?.to_string(); let filter = input .parse::()? .stream() .into_iter() .map(|v| match v { proc_macro2::TokenTree::Group(_) => String::new(), proc_macro2::TokenTree::Ident(ident) => ident.to_string(), proc_macro2::TokenTree::Punct(_) => String::new(), proc_macro2::TokenTree::Literal(_) => String::new(), }) .filter(|p| !p.is_empty()) .collect(); let mut stream = input.parse::()?.stream().into_iter(); let mut elements = vec![]; let mut element_name = String::new(); loop { match stream.next() { Some(v) => match v { proc_macro2::TokenTree::Ident(ident) => { element_name = ident.to_string(); } proc_macro2::TokenTree::Literal(lit) => { let index = lit.to_string().parse().unwrap(); elements.push((element_name.clone(), index)); } _ => (), }, None => break, } } Ok(MakeGpioEnumDispatchMacro { name, filter, elements, }) } } /// Create an enum for erased GPIO pins, using the enum-dispatch pattern #[proc_macro] pub fn make_gpio_enum_dispatch_macro(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as MakeGpioEnumDispatchMacro); let mut arms = Vec::new(); for (gpio_type, num) in input.elements { let enum_name = quote::format_ident!("ErasedPin"); let variant_name = quote::format_ident!("Gpio{}", num); if input.filter.contains(&gpio_type) { let arm = { quote! { #enum_name::#variant_name($target) => $body } }; arms.push(arm); } else { let arm = { quote! { #[allow(unused)] #enum_name::#variant_name($target) => { panic!("Unsupported") } } }; arms.push(arm); } } let macro_name = quote::format_ident!("{}", input.name); quote! { #[doc(hidden)] #[macro_export] macro_rules! #macro_name { ($m:ident, $target:ident, $body:block) => { match $m { #(#arms)* } } } pub(crate) use #macro_name; } .into() }