From 60f0342b697cdddbab9c0e8c6d772bc7aab9de38 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 11 Feb 2023 08:55:19 +0100 Subject: [PATCH] Break out core specific codegen to bindings --- rtic-macros/Cargo.toml | 1 + rtic-macros/src/bindings.rs | 1 - rtic-macros/src/check.rs | 71 +--- rtic-macros/src/codegen.rs | 2 + rtic-macros/src/codegen/assertions.rs | 36 +- rtic-macros/src/codegen/bindings.rs | 5 + rtic-macros/src/codegen/bindings/cortex.rs | 346 ++++++++++++++++++++ rtic-macros/src/codegen/module.rs | 4 +- rtic-macros/src/codegen/pre_init.rs | 69 +--- rtic-macros/src/codegen/shared_resources.rs | 86 +---- rtic-macros/src/codegen/util.rs | 68 ---- rtic-macros/src/lib.rs | 3 +- rtic/Cargo.toml | 2 +- rtic/src/export.rs | 310 +----------------- rtic/src/export/cortex_basepri.rs | 78 +++++ rtic/src/export/cortex_source_mask.rs | 194 +++++++++++ 16 files changed, 654 insertions(+), 622 deletions(-) delete mode 100644 rtic-macros/src/bindings.rs create mode 100644 rtic-macros/src/codegen/bindings.rs create mode 100644 rtic-macros/src/codegen/bindings/cortex.rs create mode 100644 rtic/src/export/cortex_basepri.rs create mode 100644 rtic/src/export/cortex_source_mask.rs diff --git a/rtic-macros/Cargo.toml b/rtic-macros/Cargo.toml index 970f666247..12cd0d9515 100644 --- a/rtic-macros/Cargo.toml +++ b/rtic-macros/Cargo.toml @@ -24,6 +24,7 @@ proc-macro = true [features] default = [] debugprint = [] + # list of supported codegen backends thumbv6 = [] thumbv7 = [] diff --git a/rtic-macros/src/bindings.rs b/rtic-macros/src/bindings.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/rtic-macros/src/bindings.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/rtic-macros/src/check.rs b/rtic-macros/src/check.rs index a05c82e8a4..957a9b36fa 100644 --- a/rtic-macros/src/check.rs +++ b/rtic-macros/src/check.rs @@ -1,70 +1,7 @@ -use std::collections::HashSet; - -use crate::syntax::ast::App; +use crate::codegen::bindings::architecture_specific_analysis; +use crate::syntax::{analyze::Analysis, ast::App}; use syn::parse; -pub fn app(app: &App) -> 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::>(); - - 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(()) +pub fn app(app: &App, analysis: &Analysis) -> parse::Result<()> { + architecture_specific_analysis(app, analysis) } diff --git a/rtic-macros/src/codegen.rs b/rtic-macros/src/codegen.rs index 24e98ce90a..48ee5e3389 100644 --- a/rtic-macros/src/codegen.rs +++ b/rtic-macros/src/codegen.rs @@ -4,6 +4,8 @@ use quote::quote; use crate::analyze::Analysis; use crate::syntax::ast::App; +pub mod bindings; + mod assertions; mod async_dispatchers; mod hardware_tasks; diff --git a/rtic-macros/src/codegen/assertions.rs b/rtic-macros/src/codegen/assertions.rs index dd94aa6d8c..2f5dd52343 100644 --- a/rtic-macros/src/codegen/assertions.rs +++ b/rtic-macros/src/codegen/assertions.rs @@ -1,8 +1,9 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; +use super::bindings::extra_assertions; +use crate::analyze::Analysis; use crate::syntax::ast::App; -use crate::{analyze::Analysis, codegen::util}; /// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits pub fn codegen(app: &App, analysis: &Analysis) -> Vec { @@ -16,38 +17,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { stmts.push(quote!(rtic::export::assert_sync::<#ty>();)); } - let device = &app.args.device; - let chunks_name = util::priority_mask_chunks_ident(); - let no_basepri_checks: Vec<_> = app - .hardware_tasks - .iter() - .filter_map(|(_, task)| { - if !util::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: () = { - if !rtic::export::have_basepri() { - #(#no_basepri_checks)* - } else { - // TODO: Add armv7 checks here - } - }; - - let _ = _CONST_CHECK; - }; - - stmts.push(const_check); + stmts.append(&mut extra_assertions(app, analysis)); stmts } diff --git a/rtic-macros/src/codegen/bindings.rs b/rtic-macros/src/codegen/bindings.rs new file mode 100644 index 0000000000..1efe0ce0b2 --- /dev/null +++ b/rtic-macros/src/codegen/bindings.rs @@ -0,0 +1,5 @@ +// TODO: Feature gate +mod cortex; + +// TODO: Feature gate +pub use cortex::*; diff --git a/rtic-macros/src/codegen/bindings/cortex.rs b/rtic-macros/src/codegen/bindings/cortex.rs new file mode 100644 index 0000000000..15976a1087 --- /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(&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; 3] = [#(#mask_arr),*]; + + unsafe { + rtic::export::lock( + #ptr, + CEILING, + &MASKS, + f, + ) + } + } + } + ) + } + + pub fn extra_assertions(_: &App, _: &SyntaxAnalysis) -> Vec { + // 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(&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 { + vec![] + } +} + +pub fn pre_init_checks(app: &App, _: &SyntaxAnalysis) -> Vec { + 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 { + 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::>(); + + 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(()) +} diff --git a/rtic-macros/src/codegen/module.rs b/rtic-macros/src/codegen/module.rs index 8b3fca2319..af4e034679 100644 --- a/rtic-macros/src/codegen/module.rs +++ b/rtic-macros/src/codegen/module.rs @@ -17,7 +17,7 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { match ctxt { Context::Init => { fields.push(quote!( - /// Core (Cortex-M) peripherals + /// Core peripherals pub core: rtic::export::Peripherals )); @@ -25,7 +25,7 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { let device = &app.args.device; fields.push(quote!( - /// Device peripherals + /// Device peripherals (PAC) pub device: #device::Peripherals )); diff --git a/rtic-macros/src/codegen/pre_init.rs b/rtic-macros/src/codegen/pre_init.rs index 28ba29c0e6..a2d0e8c135 100644 --- a/rtic-macros/src/codegen/pre_init.rs +++ b/rtic-macros/src/codegen/pre_init.rs @@ -1,15 +1,13 @@ +use super::bindings::{pre_init_checks, pre_init_enable_interrupts}; +use crate::analyze::Analysis; use crate::syntax::ast::App; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use crate::{analyze::Analysis, codegen::util}; - /// Generates code that runs before `#[init]` pub fn codegen(app: &App, analysis: &Analysis) -> Vec { let mut stmts = vec![]; - let rt_err = util::rt_err_ident(); - // Disable interrupts -- `init` must run with interrupts disabled stmts.push(quote!(rtic::export::interrupt::disable();)); @@ -18,68 +16,9 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into(); )); - let device = &app.args.device; - let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); + stmts.append(&mut pre_init_checks(app, analysis)); - // check that all dispatchers exists in the `Interrupt` enumeration regardless of whether - // they are used or not - let interrupt = util::interrupt_ident(); - for name in app.args.dispatchers.keys() { - stmts.push(quote!(let _ = #rt_err::#interrupt::#name;)); - } - - 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 util::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 util::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.append(&mut pre_init_enable_interrupts(app, analysis)); stmts } diff --git a/rtic-macros/src/codegen/shared_resources.rs b/rtic-macros/src/codegen/shared_resources.rs index 19fd13fecc..686c2809cb 100644 --- a/rtic-macros/src/codegen/shared_resources.rs +++ b/rtic-macros/src/codegen/shared_resources.rs @@ -2,7 +2,8 @@ use crate::syntax::{analyze::Ownership, ast::App}; use crate::{analyze::Analysis, codegen::util}; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use std::collections::HashMap; + +use super::bindings::impl_mutex; /// Generates `static` variables and shared resource proxies pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { @@ -75,8 +76,9 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { // For future use // let doc = format!(" RTIC internal ({} resource): {}:{}", doc, file!(), line!()); - mod_app.push(util::impl_mutex( + mod_app.push(impl_mutex( app, + analysis, cfgs, true, &shared_name, @@ -95,86 +97,6 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { }) }; - // 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 !util::is_exception(&task.args.binds) { - Some((&task.args.priority, &task.args.binds)) - } else { - // If any resource to the exception uses non-lock-free or non-local resources this is - // not allwed on thumbv6. - uses_exceptions_with_resources = uses_exceptions_with_resources - || task - .args - .shared_resources - .iter() - .map(|(ident, access)| { - if access.is_exclusive() { - if let Some(r) = app.shared_resources.get(ident) { - !r.properties.lock_free - } else { - false - } - } else { - false - } - }) - .any(|v| v); - - 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),*]) - )); - } - - // Generate a constant for the number of chunks needed by Mask. - let chunks_name = util::priority_mask_chunks_ident(); - mod_app.push(quote!( - #[doc(hidden)] - #[allow(non_upper_case_globals)] - const #chunks_name: usize = rtic::export::compute_mask_chunks([#(#mask_ids),*]); - )); - - let masks_name = util::priority_masks_ident(); - mod_app.push(quote!( - #[doc(hidden)] - #[allow(non_upper_case_globals)] - const #masks_name: [rtic::export::Mask<#chunks_name>; 3] = [#(#mask_arr),*]; - )); - - 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!( #(#mod_app)* diff --git a/rtic-macros/src/codegen/util.rs b/rtic-macros/src/codegen/util.rs index d0c8cc0e44..27c8a2a10e 100644 --- a/rtic-macros/src/codegen/util.rs +++ b/rtic-macros/src/codegen/util.rs @@ -6,70 +6,11 @@ use syn::{Attribute, Ident, PatType}; const RTIC_INTERNAL: &str = "__rtic_internal"; -/// Generates a `Mutex` implementation -pub fn impl_mutex( - app: &App, - 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; - let masks_name = priority_masks_ident(); - quote!( - #(#cfgs)* - impl<'a> rtic::Mutex for #path<'a> { - type T = #ty; - - #[inline(always)] - fn lock(&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, - &#masks_name, - f, - ) - } - } - } - ) -} - pub fn interrupt_ident() -> Ident { let span = Span::call_site(); Ident::new("interrupt", span) } -/// Whether `name` is an exception with configurable priority -pub fn is_exception(name: &Ident) -> bool { - let s = name.to_string(); - - matches!( - &*s, - "MemoryManagement" - | "BusFault" - | "UsageFault" - | "SecureFault" - | "SVCall" - | "DebugMonitor" - | "PendSV" - | "SysTick" - ) -} - /// Mark a name as internal pub fn mark_internal_name(name: &str) -> Ident { Ident::new(&format!("{RTIC_INTERNAL}_{name}"), Span::call_site()) @@ -204,15 +145,6 @@ pub fn static_shared_resource_ident(name: &Ident) -> Ident { mark_internal_name(&format!("shared_resource_{name}")) } -/// Generates an Ident for the number of 32 bit chunks used for Mask storage. -pub fn priority_mask_chunks_ident() -> Ident { - mark_internal_name("MASK_CHUNKS") -} - -pub fn priority_masks_ident() -> Ident { - mark_internal_name("MASKS") -} - pub fn static_local_resource_ident(name: &Ident) -> Ident { mark_internal_name(&format!("local_resource_{name}")) } diff --git a/rtic-macros/src/lib.rs b/rtic-macros/src/lib.rs index 3ac27017db..cd2a9245db 100644 --- a/rtic-macros/src/lib.rs +++ b/rtic-macros/src/lib.rs @@ -8,7 +8,6 @@ use proc_macro::TokenStream; use std::{env, fs, path::Path}; mod analyze; -mod bindings; mod check; mod codegen; mod syntax; @@ -38,7 +37,7 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { Ok(x) => x, }; - if let Err(e) = check::app(&app) { + if let Err(e) = check::app(&app, &analysis) { return e.to_compile_error().into(); } diff --git a/rtic/Cargo.toml b/rtic/Cargo.toml index 9fa9f9d581..24b7fd1748 100644 --- a/rtic/Cargo.toml +++ b/rtic/Cargo.toml @@ -51,7 +51,7 @@ lm3s6965 = "0.1.3" cortex-m-semihosting = "0.5.0" rtic-time = { path = "../rtic-time" } rtic-channel = { path = "../rtic-channel" } -rtic-monotonics = { path = "../rtic-monotonics" } +rtic-monotonics = { path = "../rtic-monotonics", features = ["cortex_m_systick"] } [dev-dependencies.futures] version = "0.3.26" diff --git a/rtic/src/export.rs b/rtic/src/export.rs index cdca972785..91812fa0d8 100644 --- a/rtic/src/export.rs +++ b/rtic/src/export.rs @@ -1,89 +1,20 @@ pub use bare_metal::CriticalSection; -pub use cortex_m::{ - asm::nop, - asm::wfi, - interrupt, - peripheral::{scb::SystemHandler, DWT, NVIC, SCB, SYST}, - Peripherals, -}; //pub use portable_atomic as atomic; pub use atomic_polyfill as atomic; pub mod executor; -/// Mask is used to store interrupt masks on systems without a BASEPRI register (M0, M0+, M23). -/// It needs to be large enough to cover all the relevant interrupts in use. -/// For M0/M0+ there are only 32 interrupts so we only need one u32 value. -/// For M23 there can be as many as 480 interrupts. -/// Rather than providing space for all possible interrupts, we just detect the highest interrupt in -/// use at compile time and allocate enough u32 chunks to cover them. -#[derive(Copy, Clone)] -pub struct Mask([u32; M]); +// #[cfg(have_basepri)] +pub mod cortex_basepri; -impl core::ops::BitOrAssign for Mask { - fn bitor_assign(&mut self, rhs: Self) { - for i in 0..M { - self.0[i] |= rhs.0[i]; - } - } -} +// #[cfg(not(have_basepri))] +pub mod cortex_source_mask; -#[cfg(not(have_basepri))] -impl Mask { - /// Set a bit inside a Mask. - const fn set_bit(mut self, bit: u32) -> Self { - let block = bit / 32; - - if block as usize >= M { - panic!("Generating masks for thumbv6/thumbv8m.base failed! Are you compiling for thumbv6 on an thumbv7 MCU or using an unsupported thumbv8m.base MCU?"); - } - - let offset = bit - (block * 32); - self.0[block as usize] |= 1 << offset; - self - } -} - -#[cfg(have_basepri)] -use cortex_m::register::basepri; - -#[cfg(have_basepri)] -#[inline(always)] -pub fn run(priority: u8, f: F) -where - F: FnOnce(), -{ - if priority == 1 { - // If the priority of this interrupt is `1` then BASEPRI can only be `0` - f(); - unsafe { basepri::write(0) } - } else { - let initial = basepri::read(); - f(); - unsafe { basepri::write(initial) } - } -} - -#[cfg(not(have_basepri))] -#[inline(always)] -pub fn run(_priority: u8, f: F) -where - F: FnOnce(), -{ - f(); -} - -/// Const helper to check architecture -pub const fn have_basepri() -> bool { - #[cfg(have_basepri)] - { - true - } - - #[cfg(not(have_basepri))] - { - false - } +/// Priority conversion, takes logical priorities 1..=N and converts it to NVIC priority. +#[inline] +#[must_use] +pub const fn cortex_logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { + ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) } #[inline(always)] @@ -99,226 +30,3 @@ where T: Sync, { } - -/// Lock implementation using BASEPRI and global Critical Section (CS) -/// -/// # Safety -/// -/// The system ceiling is raised from current to ceiling -/// by either -/// - raising the BASEPRI to the ceiling value, or -/// - disable all interrupts in case we want to -/// mask interrupts with maximum priority -/// -/// Dereferencing a raw pointer inside CS -/// -/// The priority.set/priority.get can safely be outside the CS -/// as being a context local cell (not affected by preemptions). -/// It is merely used in order to omit masking in case current -/// priority is current priority >= ceiling. -/// -/// Lock Efficiency: -/// Experiments validate (sub)-zero cost for CS implementation -/// (Sub)-zero as: -/// - Either zero OH (lock optimized out), or -/// - Amounting to an optimal assembly implementation -/// - The BASEPRI value is folded to a constant at compile time -/// - CS entry, single assembly instruction to write BASEPRI -/// - CS exit, single assembly instruction to write BASEPRI -/// - priority.set/get optimized out (their effect not) -/// - On par or better than any handwritten implementation of SRP -/// -/// Limitations: -/// The current implementation reads/writes BASEPRI once -/// even in some edge cases where this may be omitted. -/// Total OH of per task is max 2 clock cycles, negligible in practice -/// but can in theory be fixed. -/// -#[cfg(have_basepri)] -#[inline(always)] -pub unsafe fn lock( - ptr: *mut T, - ceiling: u8, - nvic_prio_bits: u8, - _mask: &[Mask; 3], - f: impl FnOnce(&mut T) -> R, -) -> R { - if ceiling == (1 << nvic_prio_bits) { - let r = interrupt::free(|_| f(&mut *ptr)); - r - } else { - let current = basepri::read(); - basepri::write(logical2hw(ceiling, nvic_prio_bits)); - let r = f(&mut *ptr); - basepri::write(current); - r - } -} - -/// Lock implementation using interrupt masking -/// -/// # Safety -/// -/// The system ceiling is raised from current to ceiling -/// by computing a 32 bit `mask` (1 bit per interrupt) -/// 1: ceiling >= priority > current -/// 0: else -/// -/// On CS entry, `clear_enable_mask(mask)` disables interrupts -/// On CS exit, `set_enable_mask(mask)` re-enables interrupts -/// -/// The priority.set/priority.get can safely be outside the CS -/// as being a context local cell (not affected by preemptions). -/// It is merely used in order to omit masking in case -/// current priority >= ceiling. -/// -/// Dereferencing a raw pointer is done safely inside the CS -/// -/// Lock Efficiency: -/// Early experiments validate (sub)-zero cost for CS implementation -/// (Sub)-zero as: -/// - Either zero OH (lock optimized out), or -/// - Amounting to an optimal assembly implementation -/// - if ceiling == (1 << nvic_prio_bits) -/// - we execute the closure in a global critical section (interrupt free) -/// - CS entry cost, single write to core register -/// - CS exit cost, single write to core register -/// else -/// - The `mask` value is folded to a constant at compile time -/// - CS entry, single write of the 32 bit `mask` to the `icer` register -/// - CS exit, single write of the 32 bit `mask` to the `iser` register -/// - priority.set/get optimized out (their effect not) -/// - On par or better than any hand written implementation of SRP -/// -/// Limitations: -/// Current implementation does not allow for tasks with shared resources -/// to be bound to exception handlers, as these cannot be masked in HW. -/// -/// Possible solutions: -/// - Mask exceptions by global critical sections (interrupt::free) -/// - Temporary lower exception priority -/// -/// These possible solutions are set goals for future work -#[cfg(not(have_basepri))] -#[inline(always)] -pub unsafe fn lock( - ptr: *mut T, - ceiling: u8, - _nvic_prio_bits: u8, - masks: &[Mask; 3], - f: impl FnOnce(&mut T) -> R, -) -> R { - if ceiling >= 4 { - // safe to manipulate outside critical section - // execute closure under protection of raised system ceiling - - // safe to manipulate outside critical section - interrupt::free(|_| f(&mut *ptr)) - } else { - // safe to manipulate outside critical section - let mask = compute_mask(0, ceiling, masks); - clear_enable_mask(mask); - - // execute closure under protection of raised system ceiling - let r = f(&mut *ptr); - - set_enable_mask(mask); - - // safe to manipulate outside critical section - r - } -} - -#[cfg(not(have_basepri))] -#[inline(always)] -fn compute_mask(from_prio: u8, to_prio: u8, masks: &[Mask; 3]) -> Mask { - let mut res = Mask([0; M]); - masks[from_prio as usize..to_prio as usize] - .iter() - .for_each(|m| res |= *m); - res -} - -// enables interrupts -#[cfg(not(have_basepri))] -#[inline(always)] -unsafe fn set_enable_mask(mask: Mask) { - for i in 0..M { - // This check should involve compile time constants and be optimized out. - if mask.0[i] != 0 { - (*NVIC::PTR).iser[i].write(mask.0[i]); - } - } -} - -// disables interrupts -#[cfg(not(have_basepri))] -#[inline(always)] -unsafe fn clear_enable_mask(mask: Mask) { - for i in 0..M { - // This check should involve compile time constants and be optimized out. - if mask.0[i] != 0 { - (*NVIC::PTR).icer[i].write(mask.0[i]); - } - } -} - -#[inline] -#[must_use] -pub fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { - ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) -} - -#[cfg(have_basepri)] -pub const fn create_mask(_: [u32; N]) -> Mask { - Mask([0; M]) -} - -#[cfg(not(have_basepri))] -pub const fn create_mask(list_of_shifts: [u32; N]) -> Mask { - let mut mask = Mask([0; M]); - let mut i = 0; - - while i < N { - let shift = list_of_shifts[i]; - i += 1; - mask = mask.set_bit(shift); - } - - mask -} - -#[cfg(have_basepri)] -pub const fn compute_mask_chunks(_: [u32; L]) -> usize { - 0 -} - -/// Compute the number of u32 chunks needed to store the Mask value. -/// On M0, M0+ this should always end up being 1. -/// On M23 we will pick a number that allows us to store the highest index used by the code. -/// This means the amount of overhead will vary based on the actually interrupts used by the code. -#[cfg(not(have_basepri))] -pub const fn compute_mask_chunks(ids: [u32; L]) -> usize { - let mut max: usize = 0; - let mut i = 0; - - while i < L { - let id = ids[i] as usize; - i += 1; - - if id > max { - max = id; - } - } - (max + 32) / 32 -} - -#[cfg(have_basepri)] -pub const fn no_basepri_panic() { - // For non-v6 all is fine -} - -#[cfg(not(have_basepri))] -pub const fn no_basepri_panic() { - panic!("Exceptions with shared resources are not allowed when compiling for thumbv6 or thumbv8m.base. Use local resources or `#[lock_free]` shared resources"); -} diff --git a/rtic/src/export/cortex_basepri.rs b/rtic/src/export/cortex_basepri.rs new file mode 100644 index 0000000000..04f8ae900c --- /dev/null +++ b/rtic/src/export/cortex_basepri.rs @@ -0,0 +1,78 @@ +use super::cortex_logical2hw; +use cortex_m::register::basepri; +pub use cortex_m::{ + asm::nop, + asm::wfi, + interrupt, + peripheral::{scb::SystemHandler, DWT, NVIC, SCB, SYST}, + Peripherals, +}; + +#[inline(always)] +pub fn run(priority: u8, f: F) +where + F: FnOnce(), +{ + if priority == 1 { + // If the priority of this interrupt is `1` then BASEPRI can only be `0` + f(); + unsafe { basepri::write(0) } + } else { + let initial = basepri::read(); + f(); + unsafe { basepri::write(initial) } + } +} + +/// Lock implementation using BASEPRI and global Critical Section (CS) +/// +/// # Safety +/// +/// The system ceiling is raised from current to ceiling +/// by either +/// - raising the BASEPRI to the ceiling value, or +/// - disable all interrupts in case we want to +/// mask interrupts with maximum priority +/// +/// Dereferencing a raw pointer inside CS +/// +/// The priority.set/priority.get can safely be outside the CS +/// as being a context local cell (not affected by preemptions). +/// It is merely used in order to omit masking in case current +/// priority is current priority >= ceiling. +/// +/// Lock Efficiency: +/// Experiments validate (sub)-zero cost for CS implementation +/// (Sub)-zero as: +/// - Either zero OH (lock optimized out), or +/// - Amounting to an optimal assembly implementation +/// - The BASEPRI value is folded to a constant at compile time +/// - CS entry, single assembly instruction to write BASEPRI +/// - CS exit, single assembly instruction to write BASEPRI +/// - priority.set/get optimized out (their effect not) +/// - On par or better than any handwritten implementation of SRP +/// +/// Limitations: +/// The current implementation reads/writes BASEPRI once +/// even in some edge cases where this may be omitted. +/// Total OH of per task is max 2 clock cycles, negligible in practice +/// but can in theory be fixed. +/// +#[inline(always)] +pub unsafe fn lock( + ptr: *mut T, + ceiling: u8, + nvic_prio_bits: u8, + f: impl FnOnce(&mut T) -> R, +) -> R { + if ceiling == (1 << nvic_prio_bits) { + let r = interrupt::free(|_| f(&mut *ptr)); + r + } else { + let current = basepri::read(); + basepri::write(cortex_logical2hw(ceiling, nvic_prio_bits)); + let r = f(&mut *ptr); + basepri::write(current); + r + } +} diff --git a/rtic/src/export/cortex_source_mask.rs b/rtic/src/export/cortex_source_mask.rs new file mode 100644 index 0000000000..db5c5f71c5 --- /dev/null +++ b/rtic/src/export/cortex_source_mask.rs @@ -0,0 +1,194 @@ +pub use cortex_m::{ + asm::nop, + asm::wfi, + interrupt, + peripheral::{scb::SystemHandler, DWT, NVIC, SCB, SYST}, + Peripherals, +}; + +/// Mask is used to store interrupt masks on systems without a BASEPRI register (M0, M0+, M23). +/// It needs to be large enough to cover all the relevant interrupts in use. +/// For M0/M0+ there are only 32 interrupts so we only need one u32 value. +/// For M23 there can be as many as 480 interrupts. +/// Rather than providing space for all possible interrupts, we just detect the highest interrupt in +/// use at compile time and allocate enough u32 chunks to cover them. +#[derive(Copy, Clone)] +pub struct Mask([u32; M]); + +pub const fn create_mask(list_of_shifts: [u32; N]) -> Mask { + let mut mask = Mask([0; M]); + let mut i = 0; + + while i < N { + let shift = list_of_shifts[i]; + i += 1; + mask = mask.set_bit(shift); + } + + mask +} + +/// Compute the number of u32 chunks needed to store the Mask value. +/// On M0, M0+ this should always end up being 1. +/// On M23 we will pick a number that allows us to store the highest index used by the code. +/// This means the amount of overhead will vary based on the actually interrupts used by the code. +pub const fn compute_mask_chunks(ids: [u32; L]) -> usize { + let mut max: usize = 0; + let mut i = 0; + + while i < L { + let id = ids[i] as usize; + i += 1; + + if id > max { + max = id; + } + } + (max + 32) / 32 +} + +impl Mask { + /// Set a bit inside a Mask. + const fn set_bit(mut self, bit: u32) -> Self { + let block = bit / 32; + + if block as usize >= M { + panic!("Generating masks for thumbv6/thumbv8m.base failed! Are you compiling for thumbv6 on an thumbv7 MCU or using an unsupported thumbv8m.base MCU?"); + } + + let offset = bit - (block * 32); + self.0[block as usize] |= 1 << offset; + self + } +} + +#[inline(always)] +pub fn run(_priority: u8, f: F) +where + F: FnOnce(), +{ + f(); +} + +/// Lock implementation using interrupt masking +/// +/// # Safety +/// +/// The system ceiling is raised from current to ceiling +/// by computing a 32 bit `mask` (1 bit per interrupt) +/// 1: ceiling >= priority > current +/// 0: else +/// +/// On CS entry, `clear_enable_mask(mask)` disables interrupts +/// On CS exit, `set_enable_mask(mask)` re-enables interrupts +/// +/// The priority.set/priority.get can safely be outside the CS +/// as being a context local cell (not affected by preemptions). +/// It is merely used in order to omit masking in case +/// current priority >= ceiling. +/// +/// Dereferencing a raw pointer is done safely inside the CS +/// +/// Lock Efficiency: +/// Early experiments validate (sub)-zero cost for CS implementation +/// (Sub)-zero as: +/// - Either zero OH (lock optimized out), or +/// - Amounting to an optimal assembly implementation +/// - if ceiling == (1 << nvic_prio_bits) +/// - we execute the closure in a global critical section (interrupt free) +/// - CS entry cost, single write to core register +/// - CS exit cost, single write to core register +/// else +/// - The `mask` value is folded to a constant at compile time +/// - CS entry, single write of the 32 bit `mask` to the `icer` register +/// - CS exit, single write of the 32 bit `mask` to the `iser` register +/// - priority.set/get optimized out (their effect not) +/// - On par or better than any hand written implementation of SRP +/// +/// Limitations: +/// Current implementation does not allow for tasks with shared resources +/// to be bound to exception handlers, as these cannot be masked in HW. +/// +/// Possible solutions: +/// - Mask exceptions by global critical sections (interrupt::free) +/// - Temporary lower exception priority +/// +/// These possible solutions are set goals for future work +#[inline(always)] +pub unsafe fn lock( + ptr: *mut T, + ceiling: u8, + masks: &[Mask; 3], + f: impl FnOnce(&mut T) -> R, +) -> R { + if ceiling >= 4 { + // safe to manipulate outside critical section + // execute closure under protection of raised system ceiling + + // safe to manipulate outside critical section + interrupt::free(|_| f(&mut *ptr)) + } else { + // safe to manipulate outside critical section + let mask = compute_mask(0, ceiling, masks); + clear_enable_mask(mask); + + // execute closure under protection of raised system ceiling + let r = f(&mut *ptr); + + set_enable_mask(mask); + + // safe to manipulate outside critical section + r + } +} + +#[inline(always)] +pub const fn compute_mask( + from_prio: u8, + to_prio: u8, + masks: &[Mask; 3], +) -> Mask { + let mut res = Mask([0; M]); + + let mut idx = from_prio as usize; + + while idx < to_prio as usize { + let mut i = 0; + + while i < M { + //self.0[i] |= rhs.0[i]; + res.0[i] |= masks[idx].0[i]; + i += 1; + } + + idx += 1; + } + + // masks[from_prio as usize..to_prio as usize] + // .iter() + // .for_each(|m| res |= *m); + + res +} + +// enables interrupts +#[inline(always)] +unsafe fn set_enable_mask(mask: Mask) { + for i in 0..M { + // This check should involve compile time constants and be optimized out. + if mask.0[i] != 0 { + (*NVIC::PTR).iser[i].write(mask.0[i]); + } + } +} + +// disables interrupts +#[inline(always)] +unsafe fn clear_enable_mask(mask: Mask) { + for i in 0..M { + // This check should involve compile time constants and be optimized out. + if mask.0[i] != 0 { + (*NVIC::PTR).icer[i].write(mask.0[i]); + } + } +}