diff options
author | 2023-02-11 08:55:19 +0100 | |
---|---|---|
committer | 2023-03-01 00:35:20 +0100 | |
commit | 60f0342b697cdddbab9c0e8c6d772bc7aab9de38 (patch) | |
tree | 5bbb73e299f416f4c10adf329704b734379caa41 /rtic-macros/src/codegen/bindings/cortex.rs | |
parent | 1cda61fbda205920517f7b63af90c97c38ff9af6 (diff) | |
download | rtic-60f0342b697cdddbab9c0e8c6d772bc7aab9de38.tar.gz rtic-60f0342b697cdddbab9c0e8c6d772bc7aab9de38.tar.zst rtic-60f0342b697cdddbab9c0e8c6d772bc7aab9de38.zip |
Break out core specific codegen to bindings
Diffstat (limited to 'rtic-macros/src/codegen/bindings/cortex.rs')
-rw-r--r-- | rtic-macros/src/codegen/bindings/cortex.rs | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/rtic-macros/src/codegen/bindings/cortex.rs b/rtic-macros/src/codegen/bindings/cortex.rs new file mode 100644 index 00000000..15976a10 --- /dev/null +++ b/rtic-macros/src/codegen/bindings/cortex.rs @@ -0,0 +1,346 @@ +use crate::{ + analyze::Analysis as CodegenAnalysis, + codegen::util, + syntax::{analyze::Analysis as SyntaxAnalysis, ast::App}, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use std::collections::HashSet; +use syn::{parse, Attribute, Ident}; + +// TODO: This should be feature gated +// pub use basepri::*; +pub use source_masking::*; + +/// Whether `name` is an exception with configurable priority +fn is_exception(name: &Ident) -> bool { + let s = name.to_string(); + + matches!( + &*s, + "MemoryManagement" + | "BusFault" + | "UsageFault" + | "SecureFault" + | "SVCall" + | "DebugMonitor" + | "PendSV" + | "SysTick" + ) +} + +pub mod source_masking { + use super::*; + use std::collections::HashMap; + + /// Generates a `Mutex` implementation + pub fn impl_mutex( + app: &App, + analysis: &CodegenAnalysis, + cfgs: &[Attribute], + resources_prefix: bool, + name: &Ident, + ty: &TokenStream2, + ceiling: u8, + ptr: &TokenStream2, + ) -> TokenStream2 { + let path = if resources_prefix { + quote!(shared_resources::#name) + } else { + quote!(#name) + }; + + // Computing mapping of used interrupts to masks + let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); + + let mut prio_to_masks = HashMap::new(); + let device = &app.args.device; + // let mut uses_exceptions_with_resources = false; + + let mut mask_ids = Vec::new(); + + for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().flat_map(|task| { + if !is_exception(&task.args.binds) { + Some((&task.args.priority, &task.args.binds)) + } else { + None + } + })) { + let v: &mut Vec<_> = prio_to_masks.entry(priority - 1).or_default(); + v.push(quote!(#device::Interrupt::#name as u32)); + mask_ids.push(quote!(#device::Interrupt::#name as u32)); + } + + // Call rtic::export::create_mask([Mask; N]), where the array is the list of shifts + + let mut mask_arr = Vec::new(); + // NOTE: 0..3 assumes max 4 priority levels according to M0, M23 spec + for i in 0..3 { + let v = if let Some(v) = prio_to_masks.get(&i) { + v.clone() + } else { + Vec::new() + }; + + mask_arr.push(quote!( + rtic::export::create_mask([#(#v),*]) + )); + } + + // if uses_exceptions_with_resources { + // mod_app.push(quote!( + // #[doc(hidden)] + // #[allow(non_upper_case_globals)] + // const __rtic_internal_V6_ERROR: () = rtic::export::no_basepri_panic(); + // )); + // } + + quote!( + #(#cfgs)* + impl<'a> rtic::Mutex for #path<'a> { + type T = #ty; + + #[inline(always)] + fn lock<RTIC_INTERNAL_R>(&mut self, f: impl FnOnce(&mut #ty) -> RTIC_INTERNAL_R) -> RTIC_INTERNAL_R { + /// Priority ceiling + const CEILING: u8 = #ceiling; + const N_CHUNKS: usize = rtic::export::compute_mask_chunks([#(#mask_ids),*]); + const MASKS: [rtic::export::Mask<N_CHUNKS>; 3] = [#(#mask_arr),*]; + + unsafe { + rtic::export::lock( + #ptr, + CEILING, + &MASKS, + f, + ) + } + } + } + ) + } + + pub fn extra_assertions(_: &App, _: &SyntaxAnalysis) -> Vec<TokenStream2> { + // let device = &app.args.device; + // let no_basepri_checks: Vec<_> = app + // .hardware_tasks + // .iter() + // .filter_map(|(_, task)| { + // if !is_exception(&task.args.binds) { + // let interrupt_name = &task.args.binds; + // Some(quote!( + // if (#device::Interrupt::#interrupt_name as usize) >= (#chunks_name * 32) { + // ::core::panic!("An interrupt out of range is used while in armv6 or armv8m.base"); + // } + // )) + // } else { + // None + // } + // }) + // .collect(); + + // let const_check = quote! { + // const _CONST_CHECK: () = { + // #(#no_basepri_checks)* + // }; + + // let _ = _CONST_CHECK; + // }; + + // vec![const_check] + vec![] + } +} + +pub mod basepri { + use super::*; + + /// Generates a `Mutex` implementation + pub fn impl_mutex( + app: &App, + _analysis: &CodegenAnalysis, + cfgs: &[Attribute], + resources_prefix: bool, + name: &Ident, + ty: &TokenStream2, + ceiling: u8, + ptr: &TokenStream2, + ) -> TokenStream2 { + let path = if resources_prefix { + quote!(shared_resources::#name) + } else { + quote!(#name) + }; + + let device = &app.args.device; + quote!( + #(#cfgs)* + impl<'a> rtic::Mutex for #path<'a> { + type T = #ty; + + #[inline(always)] + fn lock<RTIC_INTERNAL_R>(&mut self, f: impl FnOnce(&mut #ty) -> RTIC_INTERNAL_R) -> RTIC_INTERNAL_R { + /// Priority ceiling + const CEILING: u8 = #ceiling; + + unsafe { + rtic::export::lock( + #ptr, + CEILING, + #device::NVIC_PRIO_BITS, + f, + ) + } + } + } + ) + } + + pub fn extra_assertions(_: &App, _: &SyntaxAnalysis) -> Vec<TokenStream2> { + vec![] + } +} + +pub fn pre_init_checks(app: &App, _: &SyntaxAnalysis) -> Vec<TokenStream2> { + let mut stmts = vec![]; + + // check that all dispatchers exists in the `Interrupt` enumeration regardless of whether + // they are used or not + let interrupt = util::interrupt_ident(); + let rt_err = util::rt_err_ident(); + + for name in app.args.dispatchers.keys() { + stmts.push(quote!(let _ = #rt_err::#interrupt::#name;)); + } + + stmts +} + +pub fn pre_init_enable_interrupts(app: &App, analysis: &CodegenAnalysis) -> Vec<TokenStream2> { + let mut stmts = vec![]; + + let interrupt = util::interrupt_ident(); + let rt_err = util::rt_err_ident(); + let device = &app.args.device; + let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); + let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); + + // Unmask interrupts and set their priorities + for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().filter_map(|task| { + if is_exception(&task.args.binds) { + // We do exceptions in another pass + None + } else { + Some((&task.args.priority, &task.args.binds)) + } + })) { + let es = format!( + "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" + ); + // Compile time assert that this priority is supported by the device + stmts.push(quote!( + const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); }; + )); + + stmts.push(quote!( + core.NVIC.set_priority( + #rt_err::#interrupt::#name, + rtic::export::logical2hw(#priority, #nvic_prio_bits), + ); + )); + + // NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended + // interrupt is implementation defined + stmts.push(quote!(rtic::export::NVIC::unmask(#rt_err::#interrupt::#name);)); + } + + // Set exception priorities + for (name, priority) in app.hardware_tasks.values().filter_map(|task| { + if is_exception(&task.args.binds) { + Some((&task.args.binds, task.args.priority)) + } else { + None + } + }) { + let es = format!( + "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" + ); + // Compile time assert that this priority is supported by the device + stmts.push(quote!( + const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); }; + )); + + stmts.push(quote!(core.SCB.set_priority( + rtic::export::SystemHandler::#name, + rtic::export::logical2hw(#priority, #nvic_prio_bits), + );)); + } + + stmts +} + +pub fn architecture_specific_analysis(app: &App, _: &SyntaxAnalysis) -> parse::Result<()> { + // Check that external (device-specific) interrupts are not named after known (Cortex-M) + // exceptions + for name in app.args.dispatchers.keys() { + let name_s = name.to_string(); + + match &*name_s { + "NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault" + | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => { + return Err(parse::Error::new( + name.span(), + "Cortex-M exceptions can't be used as `extern` interrupts", + )); + } + + _ => {} + } + } + + // Check that there are enough external interrupts to dispatch the software tasks and the timer + // queue handler + let mut first = None; + let priorities = app + .software_tasks + .iter() + .map(|(name, task)| { + first = Some(name); + task.args.priority + }) + .filter(|prio| *prio > 0) + .collect::<HashSet<_>>(); + + let need = priorities.len(); + let given = app.args.dispatchers.len(); + if need > given { + let s = { + format!( + "not enough interrupts to dispatch \ + all software tasks (need: {need}; given: {given})" + ) + }; + + // If not enough tasks and first still is None, may cause + // "custom attribute panicked" due to unwrap on None + return Err(parse::Error::new(first.unwrap().span(), s)); + } + + // Check that all exceptions are valid; only exceptions with configurable priorities are + // accepted + for (name, task) in &app.hardware_tasks { + let name_s = task.args.binds.to_string(); + match &*name_s { + "NonMaskableInt" | "HardFault" => { + return Err(parse::Error::new( + name.span(), + "only exceptions with configurable priority can be used as hardware tasks", + )); + } + + _ => {} + } + } + + Ok(()) +} |